Operators reference
Comparison
These answer a simple yes-or-no question, like comparing two numbers on a scale — is one bigger, smaller, or the same as the other? They work on numbers andon dates the same way, so “which date comes later” uses the exact same operator as “which number is bigger” — no separate set of date operators to remember.
| Operator | Inputs | What it checks |
|---|---|---|
eq(a, b) | 2 | a equals b. Works on text, numbers, booleans, and multi-choice lists (order-insensitive). |
neq(a, b) | 2 | a does not equal b. |
gt(a, b) | 2 | a is greater than b. Works on numbers and date strings. |
gte(a, b) | 2 | a is greater than or equal to b. |
lt(a, b) | 2 | a is less than b. |
lte(a, b) | 2 | a is less than or equal to b. |
between(value, low, high) | 3 | value ≥ low and value ≤ high (inclusive on both ends). Works on numbers and dates. |
Logic
These combine several yes-or-no answers the way you would in a sentence like “if it is raining andI don't have an umbrella, take a taxi” — each piece is itself true or false, and these operators combine them into one final answer.
| Operator | Inputs | What it does |
|---|---|---|
and(a, b, …) | 1 or more | True when every input is true. Takes any number of inputs. |
or(a, b, …) | 1 or more | True when at least one input is true. |
not(a) | 1 | Inverts the truth value of its input. |
if(condition, then, otherwise) | 3 | Returns the "then" value when condition is true, the "otherwise" value when false. Useful in Calculate rules. |
coalesce(a, b, …) | 1 or more | Returns the first input that is not blank. Use to fall back to a default value. |
Presence
These only ask “did anyone answer this question at all?” — completely separate from what they answered. A question left blank and a question answered with 0or “no” are very different things, and these operators are how a rule tells them apart.
| Operator | Inputs | What it checks |
|---|---|---|
isFilled(field) | 1 | True when the question has any answer — including the number 0 or the text "false". |
isBlank(field) | 1 | True when the question has no answer — it was skipped or not yet reached. |
Maths
The same operations as a pocket calculator — add, subtract, multiply, round — just applied to a form's own answers instead of numbers you type in by hand. All of them return blank when an input cannot be treated as a number, and division or remainder by zero returns blank rather than an error or a nonsense result.
| Operator | Inputs | What it computes |
|---|---|---|
add(a, b, …) | 2 or more | Sum of all inputs. |
sub(a, b) | 2 | a minus b. |
mul(a, b, …) | 2 or more | Product of all inputs. |
div(a, b) | 2 | a divided by b. Returns blank when b is zero. |
mod(a, b) | 2 | Remainder of a ÷ b. Returns blank when b is zero. |
abs(a) | 1 | Absolute (positive) value of a. |
floor(a) | 1 | Round down to the nearest whole number. |
ceil(a) | 1 | Round up to the nearest whole number. |
round(a) or round(a, digits) | 1–2 | Round to the nearest whole number, or to the specified number of decimal places (0–12). |
min(a, b, …) | 1 or more | Smallest of all inputs. |
max(a, b, …) | 1 or more | Largest of all inputs. |
Dates
These answer questions like “how many years, months or days between these two dates” — the same counting you would do on a calendar by hand, done automatically and without an off-by-one mistake. Date operators accept YYYY-MM-DDstrings (what date fields store) and full ISO datetime strings. All arithmetic runs in UTC so the server's recomputation agrees with the browser regardless of the respondent's timezone.
| Operator | Inputs | What it returns |
|---|---|---|
today() | 0 | Today's date as YYYY-MM-DD. On the server, the submission timestamp is used — not the browser clock. |
yearsBetween(from, to) | 2 | Whole years elapsed from → to (how many birthdays have passed). Pair with today() to compute age. |
monthsBetween(from, to) | 2 | Whole calendar months elapsed from → to. |
daysBetween(from, to) | 2 | Full days elapsed from → to. |
addDays(date, n) | 2 | A date n days after date. n may be negative. |
addMonths(date, n) | 2 | A date n months after date. End-of-month clamped: 31 Jan + 1 month = 28 Feb, never 3 Mar. |
formatDate(date, pattern) | 2 | Format a date for display. Supported tokens: YYYY MM DD HH mm. Example: formatDate(dob, "DD/MM/YYYY") |
Text
Basic text manipulation — joining words together, changing case, checking whether one bit of text sits inside another — the same everyday things a phone's find-and-replace or autocomplete already does for you.
| Operator | Inputs | What it returns |
|---|---|---|
concat(a, b, …) | 1 or more | Joins all inputs as text. Blank values contribute nothing — not the word "null". |
upper(a) | 1 | Converts text to UPPERCASE. |
lower(a) | 1 | Converts text to lowercase. |
trim(a) | 1 | Removes leading and trailing spaces. |
length(a) | 1 | Number of characters in text, or number of selected options in a multi-choice answer. |
contains(a, b) | 2 | Case-insensitive: does text a contain text b? Also works on multi-choice lists: does the selection include option b? |
startsWith(a, b) | 2 | Case-insensitive: does text a start with text b? |
Lists
For questions where someone can tick more than one box, or fill in the same section several times over — every household member, every issue found on a visit — these operators count, add up, or check across all of those entries at once, the way you might tally a show of hands instead of asking one person at a time.
| Operator | Inputs | What it returns |
|---|---|---|
count(list) | 1 | Number of items in the list. For a multi-choice question: how many options are selected. |
includes(list, value) | 2 | True when the list contains the given value. |
sumOf(list) | 1 | Adds every numeric item in the list. Useful on repeating sections where each entry has a number field. |
anyOf(list) | 1 | True when at least one item in the list is truthy. |
allOf(list) | 1 | True when every item in the list is truthy. True for an empty list (vacuously). |
Lookup: auto-fill from a dropdown list
When your dropdown 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.
| Operator | Inputs | What it returns |
|---|---|---|
lookup(list, field, column) | 3 | The 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. |
Cross-form references
Like a form remembering something you already told a different form, so nobody has to type the same answer twice. A form linked to a record type can read an answer given on another form for the same record. A follow-up visit form can pre-fill the district from the registration form; a score form can read the previous baseline.
| When value | Which submission is read |
|---|---|
| Latest | The most recent submission of that form for this record. |
| First | The earliest submission of that form for this record. |
| Registration | The submission that created the record. |
Calculated values are recomputed on the server
Limits enforced at publish time
These limits exist so a rule set can never get so tangled that nobody — not the person who wrote it, not the system evaluating it — can work out what it actually does. The compiler checks the complete rule set when you publish, and rules that would fail at runtime are rejected before any respondent ever sees the form.
| Limit | Value | Why |
|---|---|---|
| Rules per form | 200 | Keeps the compiled plan manageable and prevents accidental runaway automation. |
| Nodes per expression | 256 | Prevents trees so deeply nested they are unreadable to authors and slow to evaluate. |
| Expression depth | 24 levels | A 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.