Repeat Groups

Collect multiple instances of the same question set with powerful referencing.

What are Repeat Groups?

Repeat groups allow you to collect the same questions multiple times. They are perfect for household members (name, age, relationship), purchase items (product, quantity, price), and timeline events (date, description, outcome). You can reference specific instances using index notation, list all instances with custom separators, and count the total number of instances.

Referencing Instances

First instance: {family[0].name}
Last instance: {family[-1].age}
Specific index: {family[2].relationship}
Current instance: {.name}

All Instances and Count

List all names: {family[*].name} → Output: John, Mary, Sarah
Custom separator: {family[*].name|join:"; "} → Output: John; Mary; Sarah
Count instances: {family.count} → Returns: 3

Complete Example

For a repeat group named "family" with questions name, age, and relationship, you can display: "You have {family.count} family members: {family[*].name}. The oldest is {family[-1].name} at {family[-1].age} years old. Average age: {family[*].age.avg|number:1} years."

Sibling References (Dot-Prefix)

Inside a repeat group instance, use the dot-prefix syntax to reference other sub-questions within the same instance. This is useful for calculations and conditional logic that depend on values entered in the same row.

Syntax: {.sub_question_name}
Calculate example: {.price} * {.quantity} — computes line total within each instance
Display condition: {.type} === 'other' — show a follow-up only when "other" is selected in the same instance

Dynamic Instance Titles

Use $i in the group label to pull a value from a table row matching the current instance index. This lets each repeat group instance show a meaningful title instead of just a number.

Syntax: Use $i inside a table row reference — it is replaced with the 0-based instance index
Example label: Arm {armMap[$i].Name}
Result: Instance 1 → "Arm Treatment A", Instance 2 → "Arm Placebo", Instance 3 → "Arm Control"

When the group label contains { or $i, the automatic instance number (1, 2, 3…) is suppressed since the label is already dynamic. Plain labels like "Arm" still show "Arm 1", "Arm 2", etc. as before.

Nested Repeat Groups

Repeat groups can contain other repeat groups for multi-level data collection. For example, a "households" repeat group can contain a "members" repeat group, allowing you to collect multiple members per household across multiple households.

Structure: Outer repeat group → sub-questions + inner repeat group → nested sub-questions
Full feature parity: Nested sub-questions support all question types, hints, constraints, and conditional logic

Referencing Nested Repeat Group Items

Use the two-level bracket syntax to reference sub-questions inside a nested (inner) repeat group. All names refer to the "Name" field of each question, not the label.

Same-Survey References

All outer, all inner: {outer[*].inner[*].field} → lists every inner field value across all outer instances
Specific outer, all inner: {outer[0].inner[*].field} → lists inner field values from the first outer instance only
All outer, specific inner: {outer[*].inner[1].field} → second inner field from each outer instance
Specific both: {outer[2].inner[0].field} → a single value from the third outer, first inner
Count inner instances: {outer[*].inner.count} → total inner instances across all outer instances

Multi-Select Field Indexing

If the field is a multiple select question, append a bracket index to pick a specific selection from the answer array.

First selection: {outer[0].inner[0].field[0]} → first selected option
Second selection: {outer[0].inner[0].field[1]} → second selected option
Last selection: {outer[0].inner[0].field[-1]} → last selected option
Across instances: {outer[*].inner[*].field[0]} → first selection from every inner instance
Without index: {outer[0].inner[0].field} → all selections comma-separated

Aggregation

Append an aggregation function after the field name to compute across all matched values. Supported: .sum, .avg, .min, .max, .count.

Sum all: {outer[*].inner[*].score.sum} → sum of every inner score
Average per outer: {outer[0].inner[*].score.avg} → average of inner scores in the first outer instance
Count: {outer[*].inner[*].field.count} → total number of inner field values

Cross-Survey Nested References

Prefix with the survey reference to pull nested repeat group data from another survey.

List all: {s12:outer[*].inner[*].field}
With aggregation: {s12:outer[*].inner[*].score.sum}
Specific outer: {s12:outer[0].inner[*].field}

$i / $n with Nested Groups

$i (0-based) and $n (1-based) always refer to the innermost repeat group instance. They reset for each outer instance.

Example: outer has 2 instances, inner has 3 and 2 instances respectively

Outer 1, Inner 1 → $i=0, $n=1
Outer 1, Inner 2 → $i=1, $n=2
Outer 1, Inner 3 → $i=2, $n=3
Outer 2, Inner 1 → $i=0, $n=1 (resets)
Outer 2, Inner 2 → $i=1, $n=2

$i and $n are automatically substituted in all reference contexts: text labels, notes, calculations, constraints, and display conditions.

Using Repeat Group Answers as Options

You can use answers collected in a repeat group as dynamic options in other questions. This is useful when you want a later question to reference all values entered across repeat group instances.

Syntax: {group_name[*].sub_question} as a choice option
Example: If a repeat group "crops" has a sub-question "crop_name", use {crops[*].crop_name} as an option in a later single/multiple choice question to list all entered crop names