SurveyLoopr
Tips & Tricks
10 min read

XLSForm If Statement: Examples for ODK, Kobo, SurveyCTO

Mehrab Ali
Mehrab AliPublished October 19, 2026

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.

Download example form (.xlsx)All seven examples in one XLSForm, one group each. Tested with pyxform and ODK Validate; free to reuse under CC BY 4.0.Download

What is the syntax of if() in XLSForm?

typenamelabelcalculation
integerageHow old is the respondent?
calculateage_groupif(${age} >= 18, 'adult', 'child')
noteshow_groupThis respondent is classified as: ${age_group}
surveychoicessettings

Three rules cover most mistakes:

  • Lowercase if. XPath function names are case-sensitive, so IF( 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.

typenamelabelcalculation
integerchild_ageHow old is the child?
select_one gradegradeWhich grade is the child in?
calculateeligibleif(${child_age} >= 6 and ${grade} = '2', 'eligible', 'not_eligible')
noteshow_eligibleEligibility: ${eligible}
surveychoicessettings
list_namenamelabel
grade1Grade 1
grade2Grade 2
grade3Grade 3
surveychoicessettings

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:

typenamelabelcalculationconstraint
integerfcsFood Consumption Score (0 to 112). >= 0 and . <= 112
calculatefcs_groupif(${fcs} <= 21, 'poor', if(${fcs} <= 35, 'borderline', 'acceptable'))
noteshow_fcsFood consumption: ${fcs_group}
surveychoicessettings

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.

Warning:

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:

typenamelabelcalculation
select_multiple water_sourcewater_sourcesWhich water sources does the household use?
calculateuses_unsafeif(selected(${water_sources}, '3') or selected(${water_sources}, '4'), 1, 0)
noteshow_unsafeUses an unsafe source (1 = yes): ${uses_unsafe}
surveychoicessettings
list_namenamelabel
water_source1Piped water
water_source2Protected well
water_source3Unprotected well
water_source4Surface water (river or pond)
water_source-96Other
surveychoicessettings

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:

typenamelabelcalculation
select_one yes_nohas_latrineDoes the household have a latrine?
select_one yes_nohas_soapIs there soap at the handwashing place?
select_one yes_notreats_waterDoes the household treat its drinking water?
calculatewash_scoreif(${has_latrine} = '1', 2, 0) + if(${has_soap} = '1', 1, 0) + if(${treats_water} = '1', 1, 0)
noteshow_washWASH score: ${wash_score} out of 4
surveychoicessettings
list_namenamelabel
yes_no1Yes
yes_no0No
surveychoicessettings

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:

typenamelabelcalculation
textmember_nameWhat is the household member's name?
select_one sexmember_sexWhat is ${member_name}'s sex?
calculatepronounif(${member_sex} = '1', 'he', 'she')
select_one yes_noin_schoolIs ${pronoun} currently attending school?
surveychoicessettings
list_namenamelabel
sex1Male
sex2Female
yes_no1Yes
yes_no0No
surveychoicessettings

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":

typenamelabelconstraintconstraint_message
select_one duration_unitpreg_unitWill you record the pregnancy duration in weeks or months?
integerpreg_durationHow long has she been pregnant?if(${preg_unit} = '1', . <= 42, . <= 10)Weeks must be 42 or less; months must be 10 or less.
surveychoicessettings
list_namenamelabel
duration_unit1Weeks
duration_unit2Months
surveychoicessettings

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 wroteWrite insteadError you will see
IF(...)if(...)cannot handle function 'IF'
AND(a, b)a and bcannot handle function 'AND'
if(cond, 'x')if(cond, 'x', '')Requires 3 arguments but 2 provided
=if(...)if(...)Bad node ... in expression
a <> ba != bBad 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_multipleselected(${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:

PlatformWhere the if() goes
ODK (XLSForm)calculation column of a calculate row
KoboToolbox FormbuilderAdd a Calculate question, then type the expression in its calculation box
KoboToolbox (XLSForm upload)Same as ODK
SurveyCTOcalculation 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.

Tip:

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.

Build my first form free

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.