Complete Syntax Reference
IDEAL Survey maintains ODK compatibility while offering cleaner, more intuitive syntax.
Green rows indicate IDEAL-exclusive enhancements not available in standard ODK.
| Feature | ODK Syntax | IDEAL Syntax |
|---|---|---|
| Basic ref | ${question_name} | {question_name} |
| Cross-survey | pulldata('survey', 'field', 'id', 'value') | {s1:question_name} |
| Repeat instance | indexed-repeat(${name}, ${family}, position(..)) | {family[0].name} |
| Last instance | indexed-repeat(${name}, ${family}, count(${family})) | {family[-1].name} |
| Current instance | ${name} | {.name} |
| Count | count(${family}) | {family.count} |
| Sum | sum(${items}/price) | {items[*].price.sum} |
| Average | sum(${items}/price) div count(${items}) | {items[*].price.avg} |
| Min/Max | min(${items}/price) | {items[*].price.min} |
| Arithmetic | ${price} * ${quantity} | {price} * {quantity} |
| Conditional | if(${age} >= 18, 'Adult', 'Minor') | {age} >= 18 ? 'Adult' : 'Minor' |
| Complex calc | (${baseline} - ${followup}) div ${baseline} * 100 | ({baseline} - {followup}) / {baseline} * 100 |
| Table cell | Not supported | {table_name[0].column_name} |
| Table column | Not supported | {table_name[*].column_name} |
| Table last row | Not supported | {table_name[-1].column_name} |
| Table col agg | Not supported | {table_name[*].column_name.avg} |
| Table row count | Not supported | {table_name.rows} |
| Cross-survey table | Not supported | {survey_name:table_name[*].column_name.sum} |
| Nested repeat ref | Not supported | {outer[*].inner[*].field} |
| Nested repeat agg | Not supported | {outer[*].inner[*].field.sum} |
| Cross-survey nested | Not supported | {s12:outer[*].inner[*].field.sum} |
| Nested multi-select | Not supported | {outer[0].inner[0].field[1]} |
| Dynamic options | Not supported | {question_name} as an option value |
| Dynamic table rows | Not supported | {question_name} as a row label |
| Dynamic table cols | Not supported | {question_name} as a column label |
| Bulk columns | Not supported | Column: A, B, C (comma-separated in editor) |
| Instance title | Not supported | {table[$i].column} in group label |
| Sibling ref | Not supported | {.sub_question_name} |
| Table default val | Not supported | {calc_field} as number column default |
| All instances | Manual iteration | {family[*].name} |
| Filter and agg | Complex workarounds | {family[age>18].income.avg} |
| Custom sep | Not supported | {family[*].name|join:"; "} |
| Text format | Limited | {name|uppercase} |
| Number format | Not supported | {score|number:2} |
| Truncate | Not supported | {text|truncate:50} |
| Fallback | Not supported | {q1 ?? 'N/A'} |
| Chained ops | Not supported | {score|number:2 ?? '0.00'} |
| Rich text | Limited HTML | **bold** *italic* __underline__ [color:blue]text[/color] |
All Supported Functions
Note: Functions like round(), abs(), max(), min() and arithmetic operators are only available in Calculate question types. In regular question text, you can use formatters (|uppercase, |number:2) and aggregations (.sum, .avg) but not arithmetic operations.
| Category | Function | Description | Example |
|---|---|---|---|
| Aggregate | .sum | Add all values | {items[*].price.sum} |
.avg | Calculate average | {scores[*].value.avg} | |
.min | Find minimum | {prices[*].amount.min} | |
.max | Find maximum | {scores[*].value.max} | |
.count | Count instances | {family.count} | |
| Text Format | |uppercase | Convert to uppercase | {name|uppercase} |
|lowercase | Convert to lowercase | {name|lowercase} | |
|capitalize | Capitalize first letter | {name|capitalize} | |
|trim | Remove leading/trailing spaces | {text|trim} | |
|truncate:N | Limit to N characters with ... | {text|truncate:50} | |
|join:"sep" | Join array with custom separator | {names|join:"; "} | |
| Number Format | |number:N | Format with N decimal places | {score|number:2} |
round() | Round to nearest integer | round({value}) | |
abs() | Absolute value | abs({diff}) | |
max(a,b) | Maximum of two values | max({a}, {b}) | |
min(a,b) | Minimum of two values | min({a}, {b}) |
Arithmetic Operations
Use standard arithmetic operators in Calculate question types to perform mathematical operations.
Comparison Operators
Use comparison operators in constraints and conditional logic to evaluate conditions.
Validation Constraints
Add constraints to questions to enforce data quality. The constraint expression must evaluate to true for the answer to be accepted. Use . to refer to the current question's value.
Conditional Constraints
Use the pattern !condition || validation to only enforce a rule when a condition is met. The constraint passes if the condition is false OR the validation is true.
Conditional Logic
Use ternary operators for conditional calculations in Calculate question types.
Filtering Repeat Groups
Filter repeat group instances based on conditions before aggregating or displaying. This is an IDEAL-exclusive feature.
Practical Examples
| Use Case | Syntax |
|---|---|
| Display previous answer | You entered: {name} |
| Calculate total price | {price} * {quantity} |
| Sum all prices | {items[*].price.sum} |
| Average income for adults | {family[age>18].income.avg} |
| Format score with fallback | {score|number:2 ?? '0.00'} |
| Reference previous survey | Baseline score: {baseline:total_score} |
| List names with custom separator | {family[*].name|join:"; "} |
| Senior discount calculation | {age} >= 65 ? {price} * 0.8 : {price} |
| Dynamic options from previous answer | {crops} as a choice option → expands to each selected crop |
| Dynamic table columns from answer | {crops} as column label → one column per crop |
| Reference sibling in repeat group | {.price} * {.quantity} (within same instance) |
| Table number default from calculate | {baseline_value} as default value in number column |
| Complex calculation example | {weight} / (({height} / 100) * ({height} / 100)) | number:1 |
| Nested repeat group values | {households[*].members[*].name} |
| Nested repeat group sum | {households[*].members[*].income.sum} |
| Cross-survey nested repeat | {s12:outer[*].inner[*].field.avg} |