Auto-Populate and Calculations

Writing calculation expressions, auto-populated fields, and computed values in forms, with the frozen calculation function set

Calculation expressions derive a value from other survey answers. They are re-evaluated whenever any referenced answer changes, so the derived value stays current even if the respondent goes back and edits an earlier answer.

There are two ways to use a calculation:

ModeQuestion typeBehaviour
Calculated ValuecalculateA read-only computed result. Displayed as text; respondents cannot edit it.
Auto-Populateany other type (text, integer, decimal, select_one, date, ...)A pre-filled value that the respondent can override. Re-computed whenever dependencies change.

Both modes use the exact same expression syntax and function set.

Not the same as Default Values. A default value is evaluated once, at form start, and never changes. A calculation re-runs on every dependency change. See Default Values and the comparison table below.


Where to configure it

  1. Open your survey in the Survey Designer.
  2. Select the question in the canvas.
  3. In the right-hand Properties panel, scroll to the Advanced section.
  4. Click Calculated Value (for a calculate question) or Auto-Populate (for any other type).

The Calculation Editor provides:

  • Available Variables — questions before the current one. Click to insert a ${variable} reference.
  • Functions — the built-in function catalog. Click to insert the function name and an opening parenthesis.
  • Formula textarea — type or paste your expression. It validates as you type (unknown variables and unbalanced parentheses are flagged).
  • Quick Insert — operator buttons for + - * / ( ) = > < >= <= and or.

The editor only offers earlier questions as variables — a calculation cannot reference a question that appears later in the form.


Referencing other questions

Use ${question_name} to reference another question's answer:

${quantity} * ${unit_price}

multiplies the quantity by the unit price. The reference uses the question's variable name — never its label.

Arithmetic operators

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

div and / both divide; mod gives the remainder.

Comparison and logical operators

Calculations can produce boolean values too:

OperatorMeaningExample
=, ==equals${pregnant} = 'yes'
!=not equals${pregnant} != 'yes'
>, >=, <, <=comparisons${age} >= 18
andlogical AND${age} >= 15 and ${age} <= 49
orlogical OR${district} = 'A' or ${district} = 'B'
not(...)logical NOTnot(${pregnant} = 'yes')

Use parentheses to group: (${a} = 1 or ${b} = 1) and ${c} = 1.


The calculation function set

MQUAD implements a frozen set of calculation functions identically across the web designer/runtime, the Flutter mobile app, and the form-service backend. Each function's exact semantics (argument order, units, null behaviour) are defined in the Date and Time Reference and the Expression Reference. Below is the summary of every public calculation function, grouped by purpose.

Date calculations

FunctionPurposeExample
date-diff(unit, start, end)Completed calendar units between two dates (signed)date-diff('months', ${enrollment}, ${visit})
date-add(date, amount, unit)Add calendar units with month-end clampingdate-add(${visit_date}, 14, 'days')
date-subtract(date, amount, unit)Subtract calendar units with month-end clampingdate-subtract(${visit_date}, 14, 'days')
day(date)Day of month, 1–31day(${visit_date})
month(date)Month, 1–12month(${visit_date})
year(date)Four-digit yearyear(${visit_date})
day-of-week(date)ISO day of week, 1=Mon…7=Sunday-of-week(${visit_date})

Units for date-diff / date-add / date-subtract are string literals: 'days', 'weeks', 'months', 'years' (singular forms also accepted).

Time calculations

FunctionPurposeExample
time-diff(unit, start, end)Difference between two plain times or two datetimestime-diff('hours', ${start_time}, ${end_time})

Units: 'seconds', 'minutes', 'hours', 'days'.

Age

FunctionPurposeExample
age(dob, ref)Completed years at the reference dateage(${dob}, ${interview_date})
age-months(dob, ref)Completed monthsage-months(${dob}, ${visit_date})
age-days(dob, ref)Total calendar daysage-days(${dob}, ${visit_date})

All age functions return 0 (never negative) when the reference date is on or before the date of birth.

Pregnancy / gestational age

FunctionPurposeExample
gestational-weeks(lmp, ref)Whole weeks since LMPgestational-weeks(${lmp}, ${visit_date})
gestational-days(lmp, ref)Remaining days (0–6)gestational-days(${lmp}, ${visit_date})
gestational-total-days(lmp, ref)Total days since LMPgestational-total-days(${lmp}, ${visit_date})

The reference point is the LMP (last menstrual period) → reference date — not a due date. Values are clamped to 0 if the reference is before LMP.

Health

FunctionPurposeExample
bmi(weightKg, heightCm)Body mass indexbmi(${weight_kg}, ${height_cm})

Height is always in centimetres under the frozen contract: bmi(70, 175) = 70 / (175/100)² ≈ 22.86. Do not pass metres (bmi(70, 1.75) is wrong — it returns a huge value).

Existing date/time functions (unchanged, legacy)

FunctionPurposeExample
today()Current calendar date (device-local)today()
now()Current datetime, ISO 8601now()
date(value) / date(y, m, d)Parse or construct a datedate('2026-08-14')
days-between(d1, d2)Days between two datesdays-between(${visit_1}, ${visit_2})
duration(end, start, [unit])Time difference, default hoursduration(${end}, ${start}, 'minutes')
today-offset(days)Date days from todaytoday-offset(-10)
format-date(value, fmt)Format a date with a patternformat-date(${visit_date}, '%d/%m/%Y')

days-between argument order — cross-platform caution. The legacy days-between(d1, d2) computes d2 − d1 on the web runtime, but d1 − d2 on the mobile app and form-service backend. Use date-diff('days', start, end) instead — it is frozen, consistent everywhere, and signed (date-diff('days', ${end}, ${start}) is the negation of date-diff('days', ${start}, ${end})).

duration argument order — end first! duration(end, start, [unit]) computes end − start on all platforms. It is not duration(start, end). duration(${end_time}, ${start_time}) gives a positive duration.

Numeric functions

FunctionPurposeExample
sum(a, b, ...)Sum of valuessum(${q1}, ${q2}, ${q3})
min(a, b, ...)Minimummin(${q1}, ${q2})
max(a, b, ...)Maximummax(${q1}, ${q2})
round(v, n)Round to n decimalsround(${result}, 2)
abs(v)Absolute valueabs(${change})
int(v)Truncate to integerint(${value})
number(v)Convert to numbernumber(${text_value})
pow(base, exp)Powerpow(${radius}, 2)

String functions

FunctionPurposeExample
concat(a, b, ...)Join textconcat(${first_name}, ' ', ${last_name})
join(sep, a, b, ...)Join with separatorjoin('-', ${state}, ${district})
string(v)Convert to stringstring(${age})
string-length(v)Length of stringstring-length(${name})
substring(s, start, end?)Extract substring (web)substring(${code}, 0, 3)
contains(s, sub)Contains substringcontains(${notes}, 'urgent')
starts-with(s, prefix)Starts with prefixstarts-with(${id}, 'MH-')
ends-with(s, suffix)Ends with suffixends-with(${email}, '@example.com')
replace(s, search, repl)Replace all occurrencesreplace(${text}, '-', '')
regex(v, pattern)Test regex matchregex(${phone}, '^[0-9]{10}$')

substring / substr — cross-platform caution. On the web runtime, substring(s, start, end?) uses JavaScript semantics (0-based start, and end is an end index, not a length). The mobile runtime instead exposes substr(s, start, length?) (0-based start, third argument is a length), and the form-service backend implements substring with XPath-style 1-based indexing. Prefer contains(), starts-with(), and string-length() for cross-platform logic, or test substring on every platform you deploy to.

Selection functions

FunctionPurposeExample
selected(value, choice)Is the choice selected?selected(${symptoms}, 'fever')
count-selected(value)Number of selected optionscount-selected(${symptoms})
selected-at(value, index)Choice at 0-based indexselected-at(${symptoms}, 0)
count(value)Count itemscount(${multi})

Conditional functions

FunctionPurposeExample
if(cond, then, else)Conditional valueif(${age} >= 18, 'adult', 'child')
coalesce(a, b, ...)First non-empty valuecoalesce(${phone_2}, ${phone_1}, 'none')
true() / false()Boolean constantsif(${pregnant} = 'yes', true(), false())

Use if(), not ?:. The if(condition, then, else) function is the supported conditional in calculations on every platform. The ? : ternary syntax exists only in the Flutter expression engine and will not parse on the web runtime — do not use it in calculations.

Utility functions

FunctionPurposeExample
uuid()Random UUID v4uuid()
random()Random number 0–1random()
once(value)First computed value only (page lifetime)once(now())

Common calculation patterns

Total score:

${q1} + ${q2} + ${q3} + ${q4}

Weighted score:

(${q1} * 2) + (${q2} * 3) + (${q3} * 1)

Age from DOB (recommended):

age(${dob}, ${interview_date})

interview_date in this example is an ordinary date question you create in the survey (for example "Interview date"). MQUAD does not create or infer an interview date automatically — you must supply it as a question and reference it explicitly.

Age with today's date:

age(${dob}, today())

today() is evaluated when the calculation runs, so this age updates daily. The difference from the previous example: interview_date is fixed once the respondent answers it; today() always reflects the current date.

Conditional discount:

if(${member_status} = 'Premium', ${total} * 0.2, 0)

Composite ID:

concat(${site_code}, '-', ${participant_num})

Estimated due date (a generic date calculation — not medical advice):

date-add(${lmp}, 280, 'days')

Calculation vs Default Value

Auto-Populate (calculation)Default Value
When evaluatedEvery time dependencies changeOnce, at form start
ReactiveYes — updates when referenced values changeNo — static after form start
User overrideYes — if the field is editableYes — if the field is editable
Reference syntax${question_name}#question_name
Can call functionsYesNo — default grammar has no function calls
Best forDynamic computed valuesInitial static values

Calculations in XLSForm

Use the calculation column:

typenamelabelcalculation
calculatetotal_scoreTotal Score${q1} + ${q2} + ${q3}
integerestimated_totalEstimated Total${q1} * ${q2}

When importing, all calculations are preserved and visible in the survey designer. See XLSForm Mapping for details.

Tips

  • Use Calculated Value (type = calculate) for read-only results.
  • Use Auto-Populate on normal question types for editable pre-filled values that should stay current.
  • For static initial values that never need to update, use Default Values instead.
  • A calculation that references an unanswered question yields null/blank — see Blank and Invalid Values.
  • Calculations run identically on web forms, the mobile field app, and the backend for the frozen function set above. Platform-specific differences are flagged inline (days-between, substring, ternary).