Building forms

Rules and calculations

Rules make your form intelligent without writing any code. Compute a field's value from other answers, show or hide a question based on a condition, make a question required only in certain situations, or reject a combination of answers that cannot be correct. Every rule is built visually in the rule editor and checked in full when you publish.

The four rule kinds

KindWhat it doesExpression meaning
CalculateDerives a field's value from other answers. The respondent sees the result but cannot edit it — the formula owns the value.The formula to compute. Can reference other fields, arithmetic, date math, and lookups.
ShowShows a question only when a condition is true. While hidden the question's answer is cleared and not stored.The condition that must be true for the question to appear.
RequireMakes a question mandatory only when a condition is true. Complements Show — you can require a follow-up only when it is visible.The condition under which an answer becomes required.
ValidateBlocks submission when a condition is true, showing a message you write next to the field that caused the problem.Write the condition that is WRONG — the submission is blocked whenever this evaluates to true.

How expressions work

Expressions are trees built from four kinds of node, composed by choosing an operator and filling its inputs. Inputs can themselves be operators, field references, or constants. There is no text to type; the editor only offers operations the engine can evaluate.

Node typeWhat it isExample
LiteralA constant value you type in. Can be text, a number, true/false, or a list."Male", 18, true, ["a","b"]
FieldThe current answer to a question on this form, addressed by the question's key.date_of_birth, school_name
Cross-form referenceAn answer from another form for the same subject (record-type forms only). Resolved before evaluation — the interpreter never touches a database.registration@latest.district
OperationApplication of a built-in operator to one or more inputs.yearsBetween(date_of_birth, today())

What counts as true

Show, Require, and Validate treat the expression result as a boolean:

ValueTreated as
trueTrue
false, null (blank), "" (empty text), [] (empty list)False
0True — the number zero is a real answer, not the same as unanswered
Any non-empty stringTrue
Any non-empty listTrue
Any non-zero numberTrue

Comparison operators

Comparison operators return true or false. They work on numbers and on date strings — no need to pick a different operator for dates.

OperatorInputsWhat it checks
eq(a, b)2a equals b. Works on text, numbers, booleans, and multi-choice lists (order-insensitive).
neq(a, b)2a does not equal b.
gt(a, b)2a is greater than b. Works on numbers and date strings.
gte(a, b)2a is greater than or equal to b.
lt(a, b)2a is less than b.
lte(a, b)2a is less than or equal to b.
between(value, low, high)3value ≥ low and value ≤ high (inclusive on both ends). Works on numbers and dates.

Logic operators

OperatorInputsWhat it does
and(a, b, …)1 or moreTrue when every input is true. Takes any number of inputs.
or(a, b, …)1 or moreTrue when at least one input is true.
not(a)1Inverts the truth value of its input.
if(condition, then, otherwise)3Returns the "then" value when condition is true, the "otherwise" value when false. Useful in Calculate rules.
coalesce(a, b, …)1 or moreReturns the first input that is not blank. Use to fall back to a default value.

Presence operators

These check whether a question has any answer at all, regardless of the value.

OperatorInputsWhat it checks
isFilled(field)1True when the question has any answer — including the number 0 or the text "false".
isBlank(field)1True when the question has no answer — it was skipped or not yet reached.

Maths operators

All maths operators return blank when any input cannot be converted to a number. Division and remainder by zero return blank rather than Infinity or NaN.

OperatorInputsWhat it computes
add(a, b, …)2 or moreSum of all inputs.
sub(a, b)2a minus b.
mul(a, b, …)2 or moreProduct of all inputs.
div(a, b)2a divided by b. Returns blank when b is zero.
mod(a, b)2Remainder of a ÷ b. Returns blank when b is zero.
abs(a)1Absolute (positive) value of a.
floor(a)1Round down to the nearest whole number.
ceil(a)1Round up to the nearest whole number.
round(a) or round(a, digits)1–2Round to the nearest whole number, or to the specified number of decimal places (0–12).
min(a, b, …)1 or moreSmallest of all inputs.
max(a, b, …)1 or moreLargest of all inputs.

Date operators

Date operators accept YYYY-MM-DDstrings (what date fields store) and full ISO datetime strings. All arithmetic runs in UTC so server recomputation agrees with the browser regardless of the respondent's timezone.

OperatorInputsWhat it returns
today()0Today's date as YYYY-MM-DD. On the server, the submission timestamp is used — not the browser clock.
yearsBetween(from, to)2Whole years elapsed from → to (how many birthdays have passed). Pair with today() to compute age.
monthsBetween(from, to)2Whole calendar months elapsed from → to.
daysBetween(from, to)2Full days elapsed from → to.
addDays(date, n)2A date n days after date. n may be negative.
addMonths(date, n)2A date n months after date. End-of-month clamped: 31 Jan + 1 month = 28 Feb, never 3 Mar.
formatDate(date, pattern)2Format a date for display. Supported tokens: YYYY MM DD HH mm. Example: formatDate(dob, "DD/MM/YYYY")

Text operators

OperatorInputsWhat it returns
concat(a, b, …)1 or moreJoins all inputs as text. Blank values contribute nothing — not the word "null".
upper(a)1Converts text to UPPERCASE.
lower(a)1Converts text to lowercase.
trim(a)1Removes leading and trailing spaces.
length(a)1Number of characters in text, or number of selected options in a multi-choice answer.
contains(a, b)2Case-insensitive: does text a contain text b? Also works on multi-choice lists: does the selection include option b?
startsWith(a, b)2Case-insensitive: does text a start with text b?

List operators

These work on multi-choice answers (a list of selected values) and on repeating section answers (a list of per-entry sub-form responses).

OperatorInputsWhat it returns
count(list)1Number of items in the list. For a multi-choice question: how many options are selected.
includes(list, value)2True when the list contains the given value.
sumOf(list)1Adds every numeric item in the list. Useful on repeating sections where each entry has a number field.
anyOf(list)1True when at least one item in the list is truthy.
allOf(list)1True when every item in the list is truthy. True for an empty list (vacuously).

Lookup: auto-fill from an option list

When your option list carries extra data in its metadata columns — a UDISE code on a school list, a district code on a village list — a Calculate rule can copy that data into a read-only field the instant the respondent picks an option. No second dropdown, no transcription error.

OperatorInputsWhat it returns
lookup(list, field, column)3The value stored in column of whichever item from list the respondent picked in field. Returns blank if the question is unanswered, the item is not in the list, or that column has no value for the item.

Example: lookup("ng-schools", school_name, "udise_code") — the moment a school is selected, its UDISE code fills the read-only field. The second argument (school_name) must be a plain field reference, not a computed expression. This restriction is enforced at publish time.

Cross-form references

A form bound to a record type can read an answer given on another form for the same subject. A follow-up visit form can pre-fill the district from the registration form; a score form can read the previous baseline.

When valueWhich submission is read
LatestThe most recent submission of that form for this subject.
FirstThe earliest submission of that form for this subject.
RegistrationThe submission that created the subject record.

Calculated values are recomputed on the server

The server uses the same compiled rule set stored in the immutable form version, so it reproduces exactly what the respondent saw — no drift between browser and server.

Limits enforced at publish time

The compiler checks the complete rule set when you publish. Rules that would fail at runtime are rejected before any respondent sees the form.

LimitValueWhy
Rules per form200Keeps the compiled plan manageable and prevents accidental runaway automation.
Nodes per expression256Prevents trees so deeply nested they are unreadable to authors and slow to evaluate.
Expression depth24 levelsA limit on nesting, not total node count.

The following are also caught and reported at publish time:

  • Unknown operators — an operator name not in the built-in set is rejected. There is no way to inject custom code or eval.
  • Unknown field keys — a reference to a question that does not exist (or was deleted after the rule was written) is caught with the offending rule named.
  • Dependency cycles— if field A's Calculate rule references B and B's rule references A, publication fails and both fields are named. Detection runs across the whole rule set, not rule by rule.
  • Lookup restriction — the second argument to lookup() must be a bare field reference. An expression there would require multi-pass evaluation, which the engine does not support.

Rule templates

The rule editor offers ready-made starting points. Each template produces a valid rule that compiles as-is; you then swap a field or a constant to match your form.

TemplateKindWhat it builds
Age from date of birthCalculateyearsBetween(date_field, today()). Swap the date field to match your question.
Add two answers togetherCalculateAdds two numeric questions. Replace the second operand with another field or a constant.
Show when an answer matchesShowReveals a question when another has a specific value. Fill in the target value.
Show once something is answeredShowReveals a follow-up the moment an earlier question is filled in.
Require a follow-upRequireMakes a question mandatory once another has any answer.
Reject a value outside a rangeValidatenot(between(field, 0, 100)) — blocks submission when a number falls outside the allowed band.

Rules vs. conditional logic

The builder has two ways to make questions conditional: Conditional Logic (the simpler panel) and Rules (this page). Both can show or hide questions, but they are not duplicates.

CapabilityConditional logicRules
Show / hide a questionYesYes — Show kind
Skip to a pageYesNo
Calculated fieldsNoYes — Calculate kind
Conditional requirementNoYes — Require kind
Cross-field validationNoYes — Validate kind
Date arithmeticNoYes
Option list lookupsNoYes
Cross-form referencesNoYes
Nesting depthOne conditionUp to 24 levels

Use Conditional Logic for simple “if the answer is X, show Y” cases and for page-jump navigation. Use Rules when you need calculations, nested conditions, date arithmetic, lookups, or cross-form data.