SurveyLoopr
Tips & Tricks
11 min read

XLSForm indexed-repeat(): Read Answers from a Repeat

Mehrab Ali
Mehrab AliPublished October 29, 2026

indexed-repeat() in XLSForm is indexed-repeat(${question}, ${repeat}, n): it returns the answer to question from instance n of the repeat group repeat, counting from 1. Write it in the calculation column of a calculate row, then use that row like any other answer. It is the fix for the ODK error "This field is repeated: ... You may need to use the indexed-repeat() function", and it works the same in ODK, KoboToolbox and SurveyCTO.

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 indexed-repeat() in XLSForm?

A household survey lists every member in a repeat named member, head first. After the roster, the form needs the head's name for the consent and welfare questions:

typenamelabelcalculation
begin repeatmemberHousehold member
textmember_nameName of household member (start with the head)
integermember_ageAge in completed years
end repeatmember
calculatehead_nameindexed-repeat(${member_name}, ${member}, 1)
calculatehead_ageindexed-repeat(${member_age}, ${member}, 1)
noteshow_headHousehold head: ${head_name}, aged ${head_age}
surveychoicessettings

The three arguments, in order:

  1. The question to read, inside the repeat: ${member_name}.
  2. The repeat that holds it: ${member}, the name on the begin repeat row, not its label.
  3. The instance number: 1 is the first member, 2 the second. A number, a calculate or an expression like position(..) all work.

Inside the repeat itself you do not need indexed-repeat(): ${member_name} in a later question of the same instance already means "this member's name". You need it only outside the repeat, or to reach a different instance.

When the instance does not exist yet, for example 3 in a household with two members, the result is empty, not an error.

How do I ask about each member in a second repeat?

A common design collects the names first, then loops over them with follow-up questions. The second repeat runs once per member with repeat_count set to count(${member}), and position(..) gives the number of the current instance, so instance 3 of the second repeat reads member 3's name:

typenamelabelcalculationrepeat_count
begin repeatmemberHousehold member
textmember_nameName of household member
end repeatmember
begin repeatnet_useMosquito net usecount(${member})
calculatecurrent_nameindexed-repeat(${member_name}, ${member}, position(..))
select_one yes_noslept_under_netDid ${current_name} sleep under a mosquito net last night?
end repeatnet_use
surveychoicessettings
list_namenamelabel
yes_no1Yes
yes_no0No
surveychoicessettings

This is the pattern ODK's own repeat documentation uses. It keeps the roster short and fast to fill, and the follow-ups name each person, which cuts the "which child was this?" errors in long rosters. For more on repeat_count, see repeat groups in XLSForm.

How do I read the previous instance of the same repeat?

Use position(..) - 1 as the index. This antenatal care record checks that each visit date comes after the one before it. The first visit has no previous one, so the calculate stays empty there and the constraint lets it through:

typenamelabelcalculationconstraintconstraint_message
begin repeatvisitAntenatal care visit
calculateprev_dateif(position(..) > 1, indexed-repeat(${visit_date}, ${visit}, position(..) - 1), '')
datevisit_dateDate of this visit${prev_date} = '' or decimal-date-time(.) > decimal-date-time(${prev_date})This visit must be after the previous visit (${prev_date}).
end repeatvisit
surveychoicessettings

To copy the previous answer as a starting value instead of checking it, ODK documents a default that reads the previous instance with an XPath path. Here each new plot starts with the village of the plot before it, and the enumerator only changes it when the farmer has land elsewhere:

typenamelabeldefault
begin repeatplotPlot
textplot_villageVillage where this plot is${plot}[position() = position(current()/..) - 1]/plot_village
decimalplot_areaPlot area in decimals
end repeatplot
surveychoicessettings

A default in a repeat is evaluated once, when the instance is added. A calculate is evaluated again whenever something it depends on changes, so it would overwrite the enumerator's correction.

Warning:

Do not put a question from inside a repeat in the instance_name setting. ODK Collect stops with "This field is repeated" when it names the saved form. Use a question outside the repeat, or indexed-repeat(${member_name}, ${member}, 1) for the first member.

How do I use indexed-repeat() with nested repeats?

Add one more pair of arguments for each level: the inner repeat and the instance number in it. This farm survey has a repeat of farmers, each with a repeat of plots. Outside both, indexed-repeat(${plot_area}, ${farmer}, 1, ${plot}, 2) reads the second plot of the first farmer:

typenamelabelcalculation
begin repeatfarmerFarmer
textfarmer_nameFarmer name
begin repeatplotPlot
decimalplot_areaPlot area in decimals
end repeatplot
end repeatfarmer
calculatefirst_plot_areaindexed-repeat(${plot_area}, ${farmer}, 1, ${plot}, 1)
calculatesecond_plot_areaindexed-repeat(${plot_area}, ${farmer}, 1, ${plot}, 2)
noteshow_plotsFirst farmer's plots: ${first_plot_area} and ${second_plot_area} decimals
surveychoicessettings

The outer repeat and its number always come first. ODK accepts up to five levels this way (3, 5, 7, 9 or 11 arguments). With a pair missing, the inner repeat still has many instances and ODK Validate reports "This field is repeated".

How do I find a member by an answer instead of by position?

indexed-repeat() needs a number. When you want "the member who is the respondent" rather than "member 1", ODK documents an XPath filter on the repeat: ${member}[...]/question keeps only the instances that match the condition. Inside the brackets, name the repeat's questions without ${}:

typenamelabelcalculation
begin repeatmemberHousehold member
textmember_nameName of household member
select_one yes_nois_respondentIs this the person answering the survey?
end repeatmember
calculaterespondent_name${member}[is_respondent = '1']/member_name
noteshow_respondentRespondent: ${respondent_name}
surveychoicessettings
list_namenamelabel
yes_no1Yes
yes_no0No
surveychoicessettings

If two members match, ODK Collect stops with "This field is repeated". Prevent that with a constraint, for example count(${member}[is_respondent = '1']) <= 1 on a later question. Filters like this also work inside sum() and count(), covered in the XLSForm calculation examples.

To show the label of a select answer from a repeat ("Female" instead of 2), wrap indexed-repeat() inside jr:choice-name(), never the other way round:

typenamelabelcalculation
begin repeatmemberHousehold member
textmember_nameName of household member
select_one sexmember_sexSex
end repeatmember
calculatehead_sexjr:choice-name(indexed-repeat(${member_sex}, ${member}, 1), '${member_sex}')
noteshow_head_sexSex of household head: ${head_sex}
surveychoicessettings
list_namenamelabel
sex1Male
sex2Female
surveychoicessettings

Common indexed-repeat() mistakes and how to fix them

We ran each of these through pyxform and ODK Validate. The messages are what you will see in ODK Collect, Central or the XLSForm validator:

You wroteWrite insteadWhat you will see
${member_name} outside the repeatindexed-repeat(${member_name}, ${member}, 1)This field is repeated: ... You may need to use the indexed-repeat() function to specify which value you want.
indexed-repeat(${member}, ${member_name}, 1)Question first, repeat secondindexed-repeat(): parameter 2 must be a parent of the field in parameter 1
indexed-repeat(${member_name}, ${member})Add the instance numbercannot handle function 'indexed-repeat' requires 3, 5, 7, 9 or 11 arguments. Only 2 provided.
indexed-repeat(jr:choice-name(...), ${member}, 1)jr:choice-name(indexed-repeat(...), '${member_sex}')indexed-repeat(): first parameter must be XPath field reference
Nested repeat, indexed-repeat(${member_name}, ${hh}, 1)Add the inner pair: ..., ${hh}, 1, ${member}, 1)This field is repeated
Indexed-repeat(...)Lowercase: indexed-repeat(...)cannot handle function 'Indexed-repeat'
${members} when the repeat is named memberThe exact begin repeat nameCould not find the name 'members'.

For other compile messages, see common XLSForm errors.

Does indexed-repeat() work the same in ODK, KoboToolbox and SurveyCTO?

The function and its argument order are the same on all three: indexed-repeat(${question}, ${repeat}, n), with extra pairs for nested repeats. What differs:

PlatformWhat to know
ODK Collect and CentralIndex of the current instance is position(..). Previous-instance defaults use the ${repeat}[position() = position(current()/..) - 1]/question path
KoboToolbox (KoboCollect and Enketo web forms)Same ODK syntax, including position(..). Test web forms with two or more instances before deploying
SurveyCTOUse index() for the current instance number instead of position(..); SurveyCTO's documentation notes position(..) fails inside a non-repeating group within a repeat. Skip logic goes in relevance

A form that only uses indexed-repeat() with fixed numbers moves between all three unchanged. Replace position(..) with index() when moving a looped roster to SurveyCTO.

Why read the roster instead of asking again

Every name you pull from the roster is a name the enumerator does not type twice, with two spellings to reconcile later. Questions that say "Did Rahima sleep under a net?" instead of "Did member 3 sleep under a net?" are easier to ask and harder to answer for the wrong person. A check against the previous visit date catches a typed year at the door, while the health card is still in hand.

Tip:

You can describe the roster logic in plain language: "after the roster, ask each member about net use by name, and make each visit date later than the previous one". LooprAI writes the indexed-repeat() and position(..) rows, then compiles the form with pyxform before anything is saved.

Build my first form free

Next: How to use repeat groups in XLSForm →

Frequently Asked Questions

How do I use indexed-repeat() in XLSForm?

Add a calculate row outside the repeat with indexed-repeat(${member_name}, ${member}, 1) in the calculation column. The arguments are the question inside the repeat, the repeat's name, and the instance number counting from 1. Then use ${head_name} or whatever you named the calculate anywhere later.

How do I fix "This field is repeated" in ODK?

The form references a question inside a repeat from outside it, or from instance_name, so ODK gets several values. Wrap the reference in indexed-repeat(${question}, ${repeat}, n) to pick one instance, or use sum(), count() or join() when you want all of them.

How do I get the previous repeat instance's answer?

Use position(..) - 1 as the index: indexed-repeat(${visit_date}, ${visit}, position(..) - 1). Wrap it in if(position(..) > 1, ..., '') so the first instance stays empty. To copy it as a starting value, use ODK's default with ${repeat}[position() = position(current()/..) - 1]/question.

How does indexed-repeat() work with nested repeats?

Add the inner repeat and its instance number after the outer ones: indexed-repeat(${plot_area}, ${farmer}, 1, ${plot}, 2) reads the second plot of the first farmer. ODK accepts 3, 5, 7, 9 or 11 arguments, one pair for each level of nesting.

How do I show a choice label from a repeat?

Put indexed-repeat() inside jr:choice-name(), not the other way round: jr:choice-name(indexed-repeat(${member_sex}, ${member}, 1), '${member_sex}'). The reverse order fails with "indexed-repeat(): first parameter must be XPath field reference" because indexed-repeat() needs a question as its first argument.

Is indexed-repeat() the same in KoboToolbox and SurveyCTO?

Yes. KoboToolbox and SurveyCTO use the same indexed-repeat(${question}, ${repeat}, n) syntax as ODK. KoboToolbox also uses position(..) for the current instance number; SurveyCTO recommends index() instead, because position(..) can fail inside a group within a repeat.

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.