XLSForm If Statement: Examples for ODK, Kobo, SurveyCTO
An XLSForm if statement is if(condition, value_if_true, value_if_false), written in the calculation column of a calculate row. ODK, KoboToolbox and SurveyCTO all read it the same way, so every example on this page works on all three. Each example below compiles with pyxform and ODK Validate, the same check ODK Central runs when you upload a form.
What is the syntax of if() in XLSForm?
| type | name | label | calculation |
|---|---|---|---|
| integer | age | How old is the respondent? | |
| calculate | age_group | if(${age} >= 18, 'adult', 'child') | |
| note | show_group | This respondent is classified as: ${age_group} |
Three rules cover most mistakes:
- Lowercase
if. XPath function names are case-sensitive, soIF(fails. - Exactly three arguments. There is no two-argument form. Use
''for "nothing" in the false position. - No leading
=. XLSForm is not an Excel formula.=if(...)is rejected.
Text values go in quotes ('adult'). Numbers do not (if(${age} >= 18, 1, 0)).
How do I combine conditions with AND and OR?
XLSForm uses and and or as operators between conditions, not as functions. This example comes from a common KoboToolbox forum question: a child is eligible when aged 6 or over and in grade 2.
| type | name | label | calculation |
|---|---|---|---|
| integer | child_age | How old is the child? | |
| select_one grade | grade | Which grade is the child in? | |
| calculate | eligible | if(${child_age} >= 6 and ${grade} = '2', 'eligible', 'not_eligible') | |
| note | show_eligible | Eligibility: ${eligible} |
| list_name | name | label |
|---|---|---|
| grade | 1 | Grade 1 |
| grade | 2 | Grade 2 |
| grade | 3 | Grade 3 |
Add parentheses when you mix and with or, so the form reads the way you mean it: (${a} = '1' or ${b} = '1') and ${age} >= 18.
The comparison operators are =, !=, <, <=, >, >=. A select_one answer is the choice's name, so ${grade} = '2' compares against the name column, not the label "Grade 2".
How do I write a nested if statement?
Put a second if() in the false position of the first. Each extra level adds one more outcome. This classifies a Food Consumption Score with WFP's standard cut-offs of 21 and 35:
| type | name | label | calculation | constraint |
|---|---|---|---|---|
| integer | fcs | Food Consumption Score (0 to 112) | . >= 0 and . <= 112 | |
| calculate | fcs_group | if(${fcs} <= 21, 'poor', if(${fcs} <= 35, 'borderline', 'acceptable')) | ||
| note | show_fcs | Food consumption: ${fcs_group} |
Read nested ifs from the outside in: the first condition that is true wins. So order the checks from the lowest band to the highest (or the reverse), never mixed.
Excel habits are the main source of if() errors. IF, AND(a, b), <>, & and a / b all look right to a spreadsheet user, and all of them fail in ODK. The table further down gives the XLSForm version of each, with the error you will see.
How do I use if() with a select_multiple question?
A select_multiple answer is a space-separated list of names, such as 1 4, so ${water_sources} = '4' is only true when nothing else was ticked. Use selected(question, 'name') instead:
| type | name | label | calculation |
|---|---|---|---|
| select_multiple water_source | water_sources | Which water sources does the household use? | |
| calculate | uses_unsafe | if(selected(${water_sources}, '3') or selected(${water_sources}, '4'), 1, 0) | |
| note | show_unsafe | Uses an unsafe source (1 = yes): ${uses_unsafe} |
| list_name | name | label |
|---|---|---|
| water_source | 1 | Piped water |
| water_source | 2 | Protected well |
| water_source | 3 | Unprotected well |
| water_source | 4 | Surface water (river or pond) |
| water_source | -96 | Other |
To count how many options were ticked, use count-selected(${water_sources}) instead of chaining ifs.
How do I calculate a score with if()?
Each if() returns a number, so you can add them. This is the standard pattern for weighted scores and simple composite indices:
| type | name | label | calculation |
|---|---|---|---|
| select_one yes_no | has_latrine | Does the household have a latrine? | |
| select_one yes_no | has_soap | Is there soap at the handwashing place? | |
| select_one yes_no | treats_water | Does the household treat its drinking water? | |
| calculate | wash_score | if(${has_latrine} = '1', 2, 0) + if(${has_soap} = '1', 1, 0) + if(${treats_water} = '1', 1, 0) | |
| note | show_wash | WASH score: ${wash_score} out of 4 |
| list_name | name | label |
|---|---|---|
| yes_no | 1 | Yes |
| yes_no | 0 | No |
This pattern is also safe with skipped questions. An unanswered question is empty, '' = '1' is false, and the if() returns 0. Adding raw answers (${a} + ${b}) gives a blank result as soon as one of them is empty. If you need raw numbers, wrap each one in coalesce(${a}, 0). The XLSForm calculations guide covers this in more detail.
Can I use if() to change question text?
Yes. Labels cannot contain logic, but they can show a calculate. Work the wording out in a calculate, then reference it:
| type | name | label | calculation |
|---|---|---|---|
| text | member_name | What is the household member's name? | |
| select_one sex | member_sex | What is ${member_name}'s sex? | |
| calculate | pronoun | if(${member_sex} = '1', 'he', 'she') | |
| select_one yes_no | in_school | Is ${pronoun} currently attending school? |
| list_name | name | label |
|---|---|---|
| sex | 1 | Male |
| sex | 2 | Female |
| yes_no | 1 | Yes |
| yes_no | 0 | No |
Do I need if() in relevant and constraint columns?
Usually not. The relevant and constraint columns already expect a true/false condition, so write the condition directly: ${age} >= 18, not if(${age} >= 18, true(), false()). See skip logic in XLSForm for the relevant column.
if() earns its place in a constraint when the rule itself depends on another answer. Here the allowed maximum depends on the unit the enumerator picked. Inside a constraint, . means "this question's answer":
| type | name | label | constraint | constraint_message |
|---|---|---|---|---|
| select_one duration_unit | preg_unit | Will you record the pregnancy duration in weeks or months? | ||
| integer | preg_duration | How long has she been pregnant? | if(${preg_unit} = '1', . <= 42, . <= 10) | Weeks must be 42 or less; months must be 10 or less. |
| list_name | name | label |
|---|---|---|
| duration_unit | 1 | Weeks |
| duration_unit | 2 | Months |
More validation patterns are in the XLSForm constraint examples.
Common if() errors and how to fix them
These are the messages ODK Validate returns, which is what you see when ODK Central, KoboToolbox or the XLSForm converter rejects a form:
| You wrote | Write instead | Error you will see |
|---|---|---|
IF(...) | if(...) | cannot handle function 'IF' |
AND(a, b) | a and b | cannot handle function 'AND' |
if(cond, 'x') | if(cond, 'x', '') | Requires 3 arguments but 2 provided |
=if(...) | if(...) | Bad node ... in expression |
a <> b | a != b | Bad node ... in expression |
${a} / ${b} | ${a} div ${b} | Unexpected beginning of path |
'Dr ' & ${name} | concat('Dr ', ${name}) | Couldn't understand the expression |
${q} = '4' on select_multiple | selected(${q}, '4') | No error, but the result is wrong |
The last row is the dangerous one: the form compiles and the logic is silently wrong for every respondent who ticks more than one option. For other messages, see common XLSForm errors, or paste your form into the free XLSForm validator.
Does if() work the same in ODK, KoboToolbox and SurveyCTO?
Yes. All three use the ODK XPath function if(condition, value_if_true, value_if_false). What differs is where you type it:
| Platform | Where the if() goes |
|---|---|
| ODK (XLSForm) | calculation column of a calculate row |
| KoboToolbox Formbuilder | Add a Calculate question, then type the expression in its calculation box |
| KoboToolbox (XLSForm upload) | Same as ODK |
| SurveyCTO | calculation column of a calculate field (skip logic goes in a column named relevance) |
A form built with these examples moves between the three platforms without changes to its logic.
Why conditional logic belongs in the form
Every if() you run during the interview is a classification you never have to rebuild in Stata or R, and the enumerator sees the result while the respondent is still there. A note that shows "Eligibility: not_eligible" catches a mistyped age on the spot. A cleaning script finds the same mistake weeks later, when nobody can fix it.
You can describe the rule in plain language instead: "classify the food consumption score as poor up to 21, borderline up to 35, acceptable above". LooprAI writes the calculate row, then compiles the form with pyxform before anything is saved, so a capital IF or a missing argument never reaches your form.
Next: XLSForm calculation examples →
Frequently Asked Questions
How do I write an if statement in XLSForm?
Add a calculate row and write if(condition, value_if_true, value_if_false) in its calculation column, for example if(${age} >= 18, 'adult', 'child'). Use lowercase if, single or double quotes around text, and always give exactly three arguments.
How do I use AND and OR in an XLSForm if statement?
Write and and or as operators between conditions: if(${age} >= 6 and ${grade} = '2', 'eligible', 'not_eligible'). Excel-style AND(a, b) fails with the error cannot handle function 'AND'. Add parentheses when you mix and with or.
Why does my XLSForm show 'cannot handle function IF'?
XLSForm functions are case-sensitive and the function is lowercase if. Change IF( to if( and the form compiles. The same error appears for AND, OR and other Excel function names that have no XLSForm equivalent.
How do I check a select_multiple answer in an if statement?
Use selected(${question}, 'name'), for example if(selected(${water_sources}, '4'), 1, 0). Comparing with = only matches when that one option is ticked alone, because a select_multiple answer is a space-separated list of every option ticked.
Can an XLSForm if statement have more than two outcomes?
Yes, by nesting: put another if() in the false position, as in if(${fcs} <= 21, 'poor', if(${fcs} <= 35, 'borderline', 'acceptable')). The first condition that is true decides the result, so order the checks from one end of the range to the other.
Is the if() syntax the same in KoboToolbox and SurveyCTO?
Yes. ODK, KoboToolbox and SurveyCTO all use if(condition, value_if_true, value_if_false). In Kobo's Formbuilder you type it in a Calculate question; in XLSForm and SurveyCTO it goes in the calculation column. SurveyCTO names its skip logic column relevance.
No Credit Card Required
Build the form first. Add hosting when you need it.
Start free with LooprAI, then deploy a managed ODK Central server or run DataSnap checks when your project is ready.
Automation handles the mechanical work.
Researchers decide what the evidence means.