XLSForm Select Multiple Skip Logic with selected()
To show a question when an option of a select_multiple question was ticked, write selected(${question}, 'choice_name') in its relevant column, for example selected(${water_sources}, '4'). A plain ${water_sources} = '4' looks right but only works when that one option is ticked alone. ODK, KoboToolbox and SurveyCTO all read selected() the same way, and every example below compiles with pyxform and ODK Validate, the check ODK Central runs when you upload a form.
Why does = not work on a select_multiple question?
A select_multiple answer is stored as one text value: the names of every ticked option, separated by spaces. A household that uses piped water and a river is saved as 1 4. So ${water_sources} = '4' asks "is the whole answer exactly 4?", which is false as soon as a second option is ticked. The form compiles and gives no error; the follow-up just never appears for most respondents.
This is the most common select_multiple question on the ODK and KoboToolbox forums. A typical case: an "Other" follow-up with ${qst} = '99' that works on a select_one and stops working when the question is changed to select_multiple.
selected() checks whether one name is in the list, whatever else was ticked:
| type | name | label | relevant |
|---|---|---|---|
| select_multiple water_source | water_sources | Which water sources does the household use? | |
| integer | river_distance | How many minutes does it take to walk to the river or pond? | selected(${water_sources}, '4') |
| 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 |
Three rules cover most mistakes:
- Lowercase
selected. Function names are case-sensitive, soSelected(fails. - Two arguments. The question reference first, then one choice name.
- The choice name in quotes. Use
'4', not4and not the label "Surface water".
How do I show a question when any of several options is ticked?
Join one selected() per option with or. Here a care-seeking question appears when the child had fever, cough or diarrhoea, but not when the only symptom was a rash:
| type | name | label | relevant |
|---|---|---|---|
| select_multiple symptom | symptoms | Which symptoms did the child have in the last two weeks? | |
| select_one yes_no | sought_care | Did you seek advice or treatment for the illness? | selected(${symptoms}, '1') or selected(${symptoms}, '2') or selected(${symptoms}, '3') |
| list_name | name | label |
|---|---|---|
| symptom | 1 | Fever |
| symptom | 2 | Cough |
| symptom | 3 | Diarrhoea |
| symptom | 4 | Skin rash |
| symptom | -96 | Other |
| yes_no | 1 | Yes |
| yes_no | 0 | No |
Each selected() takes one name only. selected(${symptoms}, '1' or '2') is a common shortcut, and it fails.
How do I show a question only when two options are both ticked?
Use and instead of or. This asks about crop-livestock integration only for households that earn from both crop farming and livestock:
| type | name | label | relevant |
|---|---|---|---|
| select_multiple income_source | income_sources | What were the household's sources of income in the last 12 months? | |
| select_one yes_no | uses_manure | Does the household use livestock manure on its crops? | selected(${income_sources}, '1') and selected(${income_sources}, '2') |
| list_name | name | label |
|---|---|---|
| income_source | 1 | Crop farming |
| income_source | 2 | Livestock |
| income_source | 3 | Wage labour |
| income_source | 4 | Petty trade |
| income_source | 5 | Remittances |
and and or are lowercase operators in XLSForm, not Excel functions. When you mix them, add parentheses so the form reads the way you mean it.
How do I show a question when an option was NOT selected?
Wrap selected() in not(). A KoboToolbox forum user wanted a follow-up to appear when apple or banana was ticked, but never when grapes or orange were ticked as well. The same pattern in a WASH survey: ask about water storage only when the household uses an improved source (1 or 2) and no unimproved one (3 or 4):
| type | name | label | relevant |
|---|---|---|---|
| select_multiple water_source | water_sources | Which water sources does the household use? | |
| select_one yes_no | covered_storage | Is drinking water stored in a covered container? | (selected(${water_sources}, '1') or selected(${water_sources}, '2')) and not(selected(${water_sources}, '3')) and not(selected(${water_sources}, '4')) |
The parentheses around the or part matter. Without them, and binds first and a household with piped water and a river would still see the question.
not is a function, so it needs its own brackets: not(selected(...)). Writing not selected(...) is rejected.
A select_multiple question that was skipped or left empty is not "not selected" in a useful sense: not(selected(${q}, '4')) is true for an empty answer too. If the follow-up only makes sense after the question was answered, add and ${q} != '' to the condition.
How do I show a question based on how many options were ticked?
Use count-selected(), which returns the number of ticked options. Asking for the main source of income only makes sense when there is more than one:
| type | name | label | relevant |
|---|---|---|---|
| select_multiple income_source | income_sources | What were the household's sources of income in the last 12 months? | |
| select_one income_source | main_income | Which of these was the main source of income? | count-selected(${income_sources}) >= 2 |
Use count-selected(${q}) = 1 for "exactly one option", and count-selected(${q}) >= 1 for "at least one option". The same function in a constraint column limits how many options can be ticked; the XLSForm constraint examples cover that.
How do I ask follow-up questions for each selected option?
Give each option its own group and put selected() on the begin group row. Every question inside inherits the condition, so you write it once per option instead of once per question:
| type | name | label | relevant |
|---|---|---|---|
| select_multiple income_source | income_sources | What were the household's sources of income in the last 12 months? | |
| begin group | livestock_block | Livestock income | selected(${income_sources}, '2') |
| integer | cattle_count | How many cattle does the household own? | |
| integer | livestock_income | How much did the household earn from livestock in the last month? | |
| end group | livestock_block | ||
| begin group | wage_block | Wage labour income | selected(${income_sources}, '3') |
| integer | wage_days | How many days of paid work did household members do in the last month? | |
| integer | wage_income | How much did the household earn from wage labour in the last month? | |
| end group | wage_block |
This is the same group pattern as in skip logic in XLSForm, with selected() instead of =. If you need the same set of questions for every ticked option, a repeat group is less work than one group per option.
Common select_multiple skip logic errors and how to fix them
These are the messages ODK Validate and pyxform return, which is what you see when ODK Central, KoboToolbox or the XLSForm converter rejects a form:
| You wrote | Write instead | Error you will see |
|---|---|---|
${q} = '4' | selected(${q}, '4') | No error, but the question only shows when 4 is ticked alone |
contains(${q}, '1') | selected(${q}, '1') | No error, but codes 10, 11 and 12 also match |
Selected(${q}, '4') | selected(${q}, '4') | cannot handle function 'Selected' |
selected(${q}) | selected(${q}, '4') | Requires 2 arguments but 1 provided |
selected(${q}, 4) | selected(${q}, '4') | The second parameter to the selected() function must be in quotes (like '1'). |
selected(${q}, '3' or '4') | selected(${q}, '3') or selected(${q}, '4') | The second parameter to the selected() function must be in quotes (like '1'). |
not selected(${q}, '4') | not(selected(${q}, '4')) | Couldn't understand the expression starting at this point |
selected(...) AND selected(...) | selected(...) and selected(...) | Couldn't understand the expression starting at this point |
${source} (typo in the name) | ${sources} | Could not find the name 'source'. |
The first two rows are the dangerous ones: the form compiles and the logic is silently wrong for part of your sample. Test them in preview by ticking two options at once. For other messages, see common XLSForm errors, or paste your form into the free XLSForm validator.
Does this work the same in ODK, KoboToolbox and SurveyCTO?
Yes. All three use the ODK functions selected(), not() and count-selected(), with the same arguments. What differs is where you type the condition:
| Platform | Where the condition goes |
|---|---|
| ODK (XLSForm) | relevant column of the follow-up question or group |
| KoboToolbox Formbuilder | Question Settings, then Skip Logic: use the condition builder, or choose to enter the logic in XLSForm code and type the selected() expression |
| KoboToolbox (XLSForm upload) | Same as ODK |
| SurveyCTO | Column named relevance instead of relevant; the expression is the same |
selected() also works on select_one questions, so you can use it everywhere and switch a question between select_one and select_multiple without rewriting its skip logic. The if statement guide shows the same function inside calculations.
Why select_multiple logic deserves a test with two ticks
Most people test a follow-up by ticking the one option it depends on, and that test passes even with =. The bug only shows when a real respondent ticks two options, which on questions about income sources, symptoms or water sources is most of them. A form that skips a follow-up for those households leaves a hole you only find at analysis, when you can no longer go back and ask.
You can describe the rule in plain language instead: "ask about manure only if the household earns from both crops and livestock". LooprAI writes the selected() condition, then compiles the form with pyxform before anything is saved, so a missing quote or an Excel-style AND never reaches your form.
Next: Skip logic in XLSForm →
Frequently Asked Questions
How do I add skip logic to a select_multiple question in XLSForm?
In the relevant column of the follow-up question, write selected(${question}, 'choice_name'), for example selected(${water_sources}, '4'). The question appears whenever that option is ticked, whatever else was ticked. Use the choice name from the choices sheet, in quotes.
Why does my relevant condition not work on a select_multiple question?
Most likely it uses = instead of selected(). A select_multiple answer is a space-separated list such as 1 4, so ${q} = '4' is only true when 4 is ticked alone. Change it to selected(${q}, '4') and the follow-up appears correctly.
How do I show a question when either of two options is selected?
Join two selected() calls with or: selected(${symptoms}, '1') or selected(${symptoms}, '2'). Each selected() takes one choice name only, so selected(${symptoms}, '1' or '2') fails with the error that the second parameter must be in quotes.
How do I hide a question when an option is selected?
Wrap selected() in not(): not(selected(${water_sources}, '4')). not is a function and needs its own brackets; not selected(...) is rejected. An empty answer also counts as not selected, so add and ${water_sources} != '' when the question must be answered first.
How do I check how many options were selected in XLSForm?
Use count-selected(${question}), which returns the number of ticked options. For example, count-selected(${income_sources}) >= 2 in a relevant column shows a main-income question only to households with two or more sources. It works in constraints too.
Is selected() the same in KoboToolbox and SurveyCTO?
Yes. ODK, KoboToolbox and SurveyCTO all use selected(${question}, 'name'), not() and count-selected() the same way. In Kobo's Formbuilder you set it under the question's Skip Logic settings; SurveyCTO puts skip logic in a column named relevance instead of relevant.
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.