The calculate question type runs a formula silently in the background. It stores the result in a named field that other questions can reference — but it shows no input or label to the enumerator. Use it to derive values, concatenate strings, run conditional logic, or look up data that you need later in the form.
Basic syntax
| type | name | label | calculation |
|---|---|---|---|
| calculate | total | ${price} * ${quantity} |
- type: Always
calculate. - name: A unique field name. Other questions reference this with
${name}. - label: Leave blank — calculate fields are never shown to the enumerator.
- calculation: The formula to evaluate. Supports all XLSForm functions and field references.
Common patterns
Arithmetic
| type | name | label | calculation |
|---|---|---|---|
| integer | price | Unit price | |
| integer | quantity | Quantity | |
| calculate | total | ${price} * ${quantity} | |
| note | total_note | Total: ${total} |
String concatenation
| type | name | label | calculation |
|---|---|---|---|
| text | first_name | First name | |
| text | last_name | Last name | |
| calculate | full_name | concat(${first_name}, ' ', ${last_name}) |
Conditional value
| type | name | label | calculation |
|---|---|---|---|
| integer | score | Score (0–100) | |
| calculate | grade | if(${score} >= 90, 'A', if(${score} >= 75, 'B', if(${score} >= 60, 'C', 'F'))) |
Age from date of birth
| type | name | label | calculation |
|---|---|---|---|
| date | dob | Date of birth | |
| calculate | age_years | int((today() - ${dob}) div 365.25) |
Using once() to lock the value
By default, calculate re-evaluates whenever any referenced field changes. Wrap the formula in once() to evaluate only on first load and freeze the result:
| type | name | label | calculation |
|---|---|---|---|
| calculate | interview_start | once(now()) |
Use once() for timestamps, initial values, or any case where recalculation on change is undesirable.
Pulling data from external sources
From a CSV file
pulldata('staff.csv', 'department', 'name', ${staff_id})See pulldata() for the full reference.
From a bundled SQLite database
pulldata('rawquery',
concat(${family_path}, '/locations.db::provinces'),
'SELECT name FROM provinces WHERE id = ?',
${province_id})See Local Database Search for path construction details.
From app context
pulldata('app-api', 'user.username')
pulldata('app-api', 'family_path')See App API for all available keys.
Using calculate in relevant and constraint
Other fields can reference a calculate result just like any field:
| type | name | label | relevant | constraint |
|---|---|---|---|---|
| calculate | bmi | ${weight} div (${height} * ${height}) * 10000 | ||
| note | bmi_warning | BMI is very high — please verify weight and height. | ${bmi} > 40 |
Best Practices
- Give calculate fields meaningful names —
total_costis clearer thancalc1and easier to trace in exports. - Break complex formulas into multiple chained calculate fields instead of one deeply nested expression.
- Use
once()for timestamps and initial snapshots to prevent values from changing after first capture. - Place
calculatefields before the first question that references them — evaluation order follows form order. - Keep the
labelcolumn empty. Some tools display the label; leaving it blank avoids confusing output.
Limitations
- Calculate fields are never displayed directly to the enumerator — use a
notefield to show the result. - The
calculationcolumn is required; acalculaterow with no formula always stores an empty string. - Results re-evaluate on every form change unless wrapped in
once()— very complex formulas in large forms can affect rendering performance on low-end devices. - Calculated values are stored as strings in the submission. Use
number(),int(), orboolean()to cast when passing the result to arithmetic or logic expressions.