Expression Reference

The complete reference for expression operators and every verified built-in function across web, mobile, and backend

This page is the complete reference for expression syntax: operators and every verified public function. It covers the shared grammar used by Validation, Skip Logic, and Calculations.

If you are just getting started, read Form Logic Overview first. This page is a reference, not a tutorial.


Literals

LiteralExamplesNotes
Number42, -3.14, 0Integer and decimal
String'hello', "hello"Single or double quotes
Booleantrue(), false()Also accepted bare: true, false
NullnullEmpty/missing value
Current value.Only meaningful inside constraints
Question reference${age}Another question's answer

Expression operators

Comparison

OperatorMeaningExample
=equals${sex} = 'female'
==equals (alias)${sex} == 'female'
!=not equals${sex} != 'female'
<less than${age} < 18
<=less than or equal${age} <= 65
>greater than${age} > 18
>=greater than or equal${age} >= 18

There is no <> operator. Use !=.

Logical

OperatorMeaningExample
andboth must be true${pregnant} = 'yes' and ${age} >= 18
orat least one true${district} = 'A' or ${district} = 'B'
not(...)negatesnot(${pregnant} = 'yes')
!negates (web shorthand)!${pregnant}

Arithmetic

OperatorMeaningExample
+addition${q1} + ${q2}
-subtraction${total} - ${discount}
*multiplication${qty} * ${price}
divdivision${total} div ${count}
/division${total} / ${count}
modremainder${items} mod 2

Precedence (highest to lowest)

  1. Unary: -, not, !
  2. Multiplication / division: *, /, div, mod
  3. Addition / subtraction: +, -
  4. Comparison: =, !=, <, >, <=, >=
  5. Logical AND: and
  6. Logical OR: or (lowest)

Parentheses override precedence. For complex expressions, add parentheses even when not strictly required — they make the intent clear and guard against precedence mistakes:

(${age} >= 15 and ${age} <= 49) and ${pregnant} = 'yes'

Ternary

MQUAD uses the if() function for conditional values — see below. The ? : ternary syntax exists only in the Flutter expression engine and does not parse on the web runtime, so it is not part of the supported public grammar.


Function reference

All functions listed here are verified in the MQUAD expression engines. The Platforms column shows where each function is implemented:

  • Web — web designer validation, web form runtime
  • Mobile — Flutter field app
  • Backend — survey-service validation whitelist / form-service evaluation

Unless noted, a function is available on all three. Functions marked MQUAD extension are MQUAD-specific additions; the rest are standard XLSForm/ODK functions.

Date

FunctionArgumentsReturnsExampleNotesPlatforms
date-diff(unit, start, end)integerdate-diff('months', ${enroll}, ${visit})Completed calendar units, signed. MQUAD extensionWeb, Mobile, Backend
date-add(date, amount, unit)string YYYY-MM-DDdate-add(${visit}, 14, 'days')EOM clamping. MQUAD extensionWeb, Mobile, Backend
date-subtract(date, amount, unit)string YYYY-MM-DDdate-subtract(${visit}, 14, 'days')EOM clamping. MQUAD extensionWeb, Mobile, Backend
day(date)integer 1–31day(${visit_date})MQUAD extensionWeb, Mobile, Backend
month(date)integer 1–12month(${visit_date})MQUAD extensionWeb, Mobile, Backend
year(date)integeryear(${visit_date})MQUAD extensionWeb, Mobile, Backend
day-of-week(date)integer 1–7day-of-week(${visit_date})ISO 1=Mon…7=Sun. MQUAD extensionWeb, Mobile, Backend
today()stringtoday()Device-local calendar dateWeb, Mobile, Backend
now()stringnow()ISO 8601 datetimeWeb, Mobile, Backend
date(value) or (y, m, d)stringdate('2026-08-14'), date(2026, 8, 14)Parse or constructWeb, Mobile, Backend
days-between(d1, d2)integerdays-between(${visit_1}, ${visit_2})⚠ Arg order differs by platform — see noteWeb, Mobile, Backend
duration(end, start, unit?)numberduration(${end}, ${start}, 'hours')end − start; default 'hours'Web, Mobile, Backend
today-offset(days)stringtoday-offset(-10)Date days from todayWeb, Mobile, Backend
format-date(value, fmt)stringformat-date(${visit}, '%d/%m/%Y')Web/mobile: %Y %y %m %d %b %B (mobile also %H %M %S); backend uses full strftimeWeb, Mobile, Backend

Time

FunctionArgumentsReturnsExampleNotesPlatforms
time-diff(unit, start, end)numbertime-diff('hours', ${start}, ${end})Sec/min integer, hours/days float. Mixed plain-time + datetime → null. MQUAD extensionWeb, Mobile, Backend

Age

FunctionArgumentsReturnsExampleNotesPlatforms
age(dob, ref)integer ≥ 0age(${dob}, ${interview_date})Completed years. MQUAD extensionWeb, Mobile, Backend
age-months(dob, ref)integer ≥ 0age-months(${dob}, ${visit})Completed months. MQUAD extensionWeb, Mobile, Backend
age-days(dob, ref)integer ≥ 0age-days(${dob}, ${visit})Calendar days. MQUAD extensionWeb, Mobile, Backend

Health / pregnancy

FunctionArgumentsReturnsExampleNotesPlatforms
gestational-weeks(lmp, ref)integer ≥ 0gestational-weeks(${lmp}, ${visit})Weeks since LMP. MQUAD extensionWeb, Mobile, Backend
gestational-days(lmp, ref)integer 0–6gestational-days(${lmp}, ${visit})Remainder days. MQUAD extensionWeb, Mobile, Backend
gestational-total-days(lmp, ref)integer ≥ 0gestational-total-days(${lmp}, ${visit})Total days. MQUAD extensionWeb, Mobile, Backend
bmi(weightKg, heightCm)numberbmi(${weight_kg}, ${height_cm})Height in cm. MQUAD extensionWeb, Mobile, Backend

Numeric

FunctionArgumentsReturnsExampleNotesPlatforms
sum(a, b, ...)numbersum(${q1}, ${q2})Web, Mobile, Backend
min(a, b, ...)numbermin(${q1}, ${q2})Web, Mobile, Backend
max(a, b, ...)numbermax(${q1}, ${q2})Web, Mobile, Backend
round(v, n?)numberround(${result}, 2)n = decimal placesWeb, Mobile, Backend
abs(v)numberabs(${change})Web, Mobile, Backend
int(v)integerint(${value})TruncatesWeb, Mobile, Backend
number(v)numbernumber(${text})Web, Mobile, Backend
pow(base, exp)numberpow(${radius}, 2)Web, Mobile, Backend
floor(v)integerfloor(${value})Rounds downBackend only
ceiling(v)integerceiling(${value})Rounds upBackend only
random()number 0–1random()Web, Mobile, Backend

Text / string

FunctionArgumentsReturnsExampleNotesPlatforms
concat(a, b, ...)stringconcat(${first}, ' ', ${last})Web, Mobile, Backend
join(sep, a, b, ...)stringjoin('-', ${state}, ${district})Web, Mobile, Backend
string(v)stringstring(${age})Web, Mobile, Backend
string-length(v)integerstring-length(${name})Web, Mobile, Backend
substring(s, start, end?)stringsubstring(${code}, 0, 3)⚠ Indexing differs by platformWeb, Backend
substr(s, start, length?)stringsubstr(${code}, 0, 3)Mobile name for substringMobile
contains(s, sub)booleancontains(${notes}, 'urgent')Web, Mobile, Backend
starts-with(s, prefix)booleanstarts-with(${id}, 'MH-')Web, Mobile, Backend
ends-with(s, suffix)booleanends-with(${email}, '@x.com')Web, Mobile
replace(s, search, repl)stringreplace(${text}, '-', '')Replaces all occurrencesWeb, Mobile
regex(v, pattern)booleanregex(${phone}, '^[0-9]{10}$')Web, Mobile, Backend
normalize-space(s)stringnormalize-space(${text})Collapses whitespaceBackend only
translate(s, from, to)stringtranslate(${text}, 'ae', 'AE')Character mapBackend only

Selection

FunctionArgumentsReturnsExampleNotesPlatforms
selected(value, choice)booleanselected(${symptoms}, 'fever')Web, Mobile, Backend
count-selected(value)integercount-selected(${symptoms})Web, Mobile, Backend
selected-at(value, index)stringselected-at(${symptoms}, 0)0-based indexWeb, Mobile, Backend
count(value)integercount(${multi})Web, Mobile, Backend

Conditional

FunctionArgumentsReturnsExampleNotesPlatforms
if(cond, then, else)anyif(${age} >= 18, 'adult', 'child')Web, Mobile, Backend
coalesce(a, b, ...)anycoalesce(${phone_2}, ${phone_1})First non-emptyWeb, Mobile, Backend
boolean(v)booleanboolean(${pregnant})Mobile, Backend
true()booleantrue()Web, Mobile, Backend
false()booleanfalse()Web, Mobile, Backend

Utility

FunctionArgumentsReturnsExampleNotesPlatforms
uuid()stringuuid()Random UUID v4Web, Mobile, Backend
once(v)anyonce(now())First computed value (page lifetime)Web, Mobile, Backend
position()integerposition()Position in repeatMobile, Backend
last()integerlast()Last position in repeatBackend only
empty(v)booleanempty(${notes})True when blankBackend only
pulldata(file, valueField, keyField, keyValue)stringpulldata('regions.csv', 'name', 'code', ${district})External CSV lookupMobile

Cross-platform compatibility notes

The following functions have different behaviour across platforms. They are verified as they stand; use them with care and prefer the recommended alternative where one is given.

days-between argument order (⚠ significant)

  • Web: days-between(d1, d2) computes d2 − d1 (second minus first).
  • Mobile & backend: days-between(d1, d2) computes d1 − d2 (first minus second).

Recommendation: use date-diff('days', start, end) instead — it is frozen, consistent on every platform, and signed.

substring / substr indexing

  • Web: substring(s, start, end?) — 0-based start; end is an end index (JavaScript semantics).
  • Mobile: substr(s, start, length?) — 0-based start; third argument is a length.
  • Backend: substring(s, start, length?) — XPath-style 1-based start; third argument is a length.

Recommendation: prefer contains(), starts-with(), and string-length() for cross-platform text logic.

Ternary ? :

Only the Flutter expression engine parses ? :. Use if() everywhere.

regex() anchoring

The web engine treats regex(v, pattern) as a full-string match (the pattern is anchored: ^(?:pattern)$). The mobile app and backend use a partial match (hasMatch / search). The designer's validation builder generates fully anchored patterns (regex(., '^[0-9]+$')), which behave the same everywhere. If you write regex() by hand, include the ^...$ anchors so results are identical on every platform.

selected() return type on web

The web calculation engine returns the string 'true' / 'false' from selected() (for parity with ODK), while mobile and backend return real booleans. In a boolean context (and/or/if), both strings are truthy on web — so prefer writing selected(${multi}, 'x') as a constraint or relevance expression (where it is handled correctly) rather than nesting it inside an if() calculation on web.

Backend-only functions

floor, ceiling, normalize-space, translate, last, empty, decimal-date-time exist in the backend registries but not in the web or mobile runtimes. They will not produce results in a web or mobile calculation — avoid them in survey logic.

Mobile-only functions

pulldata (external CSV lookup), substr, boolean, decimal-round, decimal-log10 are mobile-only. pulldata requires a bundled CSV file and is not available on the web runtime.


Design-time vs runtime validation

The survey designer validates expressions when you save:

  • Unknown question references are flagged.
  • Unknown function names are flagged against the backend whitelist.
  • Unbalanced quotes/parentheses are flagged.
  • Circular dependencies between expressions are detected and blocked.
  • Skip-logic scope rules (same-group + top-level references) are enforced.

These same expressions are then evaluated at runtime by the web runtime, the mobile app, and the form-service backend — a "valid at design time" expression is guaranteed to run, but its result depends on the rules above (see Blank and Invalid Values).

Client-side vs server-side evaluation (web)

On the web runtime, constraint (validation) checking while answering uses an inline checker that supports simple comparisons (${age} >= 18, . >= 5), selected(), and and / or / not() — it does not evaluate function-based constraints such as string-length(.), regex(., ...), or count-selected(...). Those are enforced by the server at submission (and by the mobile app inline). The mobile app evaluates all constraints with the full expression engine.