Default Values
Overview
Default values allow survey questions to be pre-populated with initial answers when a form starts. The system supports four expression types: question references, system variables, date/time keywords, and static values.
Default values are resolved in a deterministic phase before any calculation expressions are evaluated. They are init-time only — once resolved, they become regular editable answers and are never recomputed.
Expression Syntax
Default values use a lightweight expression language with three token types:
Question References (#varname)
References another question's answer by name:
| Expression | Result | Explanation |
|---|---|---|
#country#res_num#psu_resnum | USA001NY | Concat country + res_num + psu_resnum values (no separator) |
#fname #lname | John Doe | Concat fname + space + lname values |
hello #country world | hello USA world | Literal text mixed with references |
#username#country | JohnUSA | System variable and question reference together |
When a referenced question has no answer (empty or not yet filled), it resolves to an empty string.
System Variables (#username, #email, #deviceid)
Pre-defined variables resolved from the user's session at form start:
| Variable | Resolves To | If Unavailable |
|---|---|---|
#username | Current user's display name | Empty string |
#email | Current user's email | Empty string |
#deviceid | Device identifier | Empty string |
System variables are case-insensitive and never block form start — they silently resolve to empty when unavailable (offline mode, missing permissions, etc.).
Date/Time Keywords
| Keyword | Format | Example |
|---|---|---|
today | yyyy-MM-dd | 2026-06-12 |
now | ISO 8601 datetime | 2026-06-12T14:30:00.000Z |
today and now are whole-string keywords only. They cannot be combined with other text.
Static Values
Any expression that does not match the above patterns is treated as a static literal:
| Expression | Result |
|---|---|
42 | 42 |
Pre-filled text | Pre-filled text |
| (empty) | No default is set |
Formal Grammar
expression := TODAY | NOW | concat | static
TODAY := "today" (case-insensitive, whole string)
NOW := "now" (case-insensitive, whole string)
concat := part+
part := HASH_REF | LITERAL
HASH_REF := "#" IDENTIFIER
IDENTIFIER := [a-zA-Z_][a-zA-Z0-9_]*
LITERAL := any character sequence not part of a HASH_REF
static := any string not matching aboveAll three runtime implementations (Python, TypeScript, Dart) use this identical grammar — same input always produces the same AST, which always resolves to the same output.
Resolution Phases
Default values are resolved in a strict sequence during form initialization:
Phase 1: Load schema + prefill data
Phase 2: Resolve date/static defaults (eager — no dependencies)
Phase 3: Sort remaining defaults by order_index ASC
Phase 4: Resolve concat/system defaults in order_index order
(each resolution sees answers from all earlier defaults)
Phase 5: Evaluate calculation expressions (existing logic)
Phase 6: Render UIThe order_index field is set from the XLSForm row number during import and preserved through round-trips. If a reference points to a question with a higher order_index (appears later in the form), it resolves to an empty string at form start.
Design-Time Validation
At schema save time, default value expressions are validated against all question names in the survey:
| Condition | Behavior |
|---|---|
#ref matches a question name | Pass |
#ref matches a known system variable | Pass |
#ref is unknown (not a question, not a system variable) | Blocks save — validation error |
#today / #now keyword | Pass |
| Static value (plain text, numbers) | Pass |
| Empty / no default | Pass (no change) |
Validation runs in the validate_survey_schema() pipeline alongside schema, choice, logic, and repeat validators. A failed default validation prevents schema publication.
Additional Values
The additional_values column (separate from default) specifies extra accepted choices for select_one and select_multiple questions. These are values accepted beyond the defined choice list — commonly used for numerical "other" codes like 98 (Don't know) or 9997 (Refused).
| Column | Example | Behavior |
|---|---|---|
additional_values | 98, 9997, 9998 | Adds these as accepted choices |
additional_values | (empty) | No extra values |
Additional values are deduplicated against existing choices at render time. If a value already exists in the choice list, the duplicate is silently skipped.
Runtime Context
The resolution context is constructed at form start and contains:
| Field | Source | Guarantee |
|---|---|---|
answers | Current form data (prefill + resolved defaults) | All values are strings |
username | Auth provider | Never null — "" if unavailable |
email | Auth provider | Never null — "" if unavailable |
deviceId | Device ID provider | Never null — "" if unavailable |
API
Expression Validation Endpoint
POST /api/v1/surveys/expressions/validate{
"expression": "#country#res_num",
"expression_type": "default",
"available_fields": ["country", "res_num", "psu_resnum"]
}Response:
{
"is_valid": true,
"expression": "#country#res_num",
"expression_type": "default",
"errors": []
}The expression_type field accepts "default" in addition to "relevant", "constraint", and "calculation".
XLSForm Columns
| Column | Type | Description |
|---|---|---|
default | Text | Default value expression |
additional_values | Text | Comma-separated extra accepted choices |
order_index | Integer | Schema position (set automatically) |
The default column is part of the standard XLSForm specification. The additional_values column is an MQUAD extension. Extra columns not in the standard set are automatically preserved through import/export round-trips.
Cross-Platform Parity
The three runtime implementations are:
| Platform | File | Language |
|---|---|---|
| Backend validation | survey-service/.../default_value_parser.py | Python |
| Web form engine | frontend/.../calculation-engine.ts | TypeScript |
| Mobile field app | mobile/.../default_value_resolver.dart | Dart |
All three follow the same specification. The validateDefaultExpression() / validate() function is shared between the validation pipeline and the frontend designer UI, ensuring consistent behavior at design time and runtime.
