Default Values

Default value expression syntax, resolution rules, validation pipeline, and runtime architecture

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:

ExpressionResultExplanation
#country#res_num#psu_resnumUSA001NYConcat country + res_num + psu_resnum values (no separator)
#fname #lnameJohn DoeConcat fname + space + lname values
hello #country worldhello USA worldLiteral text mixed with references
#username#countryJohnUSASystem 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:

VariableResolves ToIf Unavailable
#usernameCurrent user's display nameEmpty string
#emailCurrent user's emailEmpty string
#deviceidDevice identifierEmpty 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

KeywordFormatExample
todayyyyy-MM-dd2026-06-12
nowISO 8601 datetime2026-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:

ExpressionResult
4242
Pre-filled textPre-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 above

All 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 UI

The 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:

ConditionBehavior
#ref matches a question namePass
#ref matches a known system variablePass
#ref is unknown (not a question, not a system variable)Blocks save — validation error
#today / #now keywordPass
Static value (plain text, numbers)Pass
Empty / no defaultPass (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).

ColumnExampleBehavior
additional_values98, 9997, 9998Adds 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:

FieldSourceGuarantee
answersCurrent form data (prefill + resolved defaults)All values are strings
usernameAuth providerNever null — "" if unavailable
emailAuth providerNever null — "" if unavailable
deviceIdDevice ID providerNever 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

ColumnTypeDescription
defaultTextDefault value expression
additional_valuesTextComma-separated extra accepted choices
order_indexIntegerSchema 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:

PlatformFileLanguage
Backend validationsurvey-service/.../default_value_parser.pyPython
Web form enginefrontend/.../calculation-engine.tsTypeScript
Mobile field appmobile/.../default_value_resolver.dartDart

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.