FHIRPath in the builder
FHIRPath is the expression language the builder uses for everything dynamic: computed answers, initial values, conditional visibility, dynamic option lists and validation. Expressions run in the browser through the bundled FHIRPath engine, evaluated live against the current form state.
What expressions run against
Every expression is evaluated against the questionnaire's live
QuestionnaireResponse. That resource is exposed as %resource, so you read
answers with normal FHIRPath, for example:
%resource.item.where(linkId='weight').answer.value
Alongside %resource, expressions can use:
%varName— any questionnaire-level variable you have defined (see below).
The builder's own engine does not provide %questionnaire or launch-context variables
such as %patient, %user or %encounter. You can still declare launch contexts so that an
SDC server can use them, and the patient presets in the top bar set the %age, %gender and similar
variables for testing.
Questionnaire variables
The Variables card above the builder tree defines reusable, questionnaire-level
FHIRPath variables (the core variable extension). Each variable
has a name and an expression and is referenced elsewhere as %name.
Variables are evaluated in order, and a later variable may reference an earlier
one. They are imported and exported as variable extensions (files that use the older sdc-questionnaire-variable address are still read). The
card's ↺ Re-init button re-evaluates all variables and item initialExpression
fields.
Launch contexts
Launch contexts declare named resources a questionnaire expects at run time —
%patient, %user, %encounter and so on (the SDC
sdc-questionnaire-launchContext extension). They are configured in the
Questionnaire properties and are what an SDC server uses to pre-populate the form
via $populate.
Where FHIRPath is used
The same engine powers several features, each backed by a different FHIR field:
| Feature | FHIR field | Purpose |
|---|---|---|
| Calculated answer | calculatedExpression (SDC) |
Compute a read-only value from other answers |
| Initial value | initialExpression (SDC) |
Seed an answer when the form loads |
| Visibility | enableWhenExpression (SDC) |
Show/hide an item — see enableWhen & visibility |
| Dynamic options | answerExpression (SDC) |
Produce choice options at render time |
| Validation | questionnaire-constraint expression |
Must return true to pass — uses %resource (%questionnaire is not available in the builder's own engine) |
Build expressions visually
You don't have to write FHIRPath by hand. Wherever an expression field appears, a 🧩 Build… button opens a small block builder that assembles the correct FHIRPath for you and shows a live result as you go — pick questions, variables and values instead of typing paths and accessors:
- Conditions (
enableWhenExpression) — shown as a tree of nested ALL (AND) / ANY (OR) groups; each leaf is one rule (question, comparison or "has answer"), with a live ✓/✗ on every node. Any expression, however nested, stays visible — unrecognised parts are editable as text only at the leaf. - Values (
calculatedExpression,initialExpression) — an arithmetic chain of questions, variables and numbers joined by × ÷ + −, plus aggregates (count, sum, average, min, max) over a repeating question's answers. - Answers → value (
calculatedExpression) — a collection pipeline over a question's answers: pick a source question, filter the answers — keep or drop values against a set (intersect/exclude), take unique values, or keep where the value compares to a number/string (> ≥ < ≤ = ≠) — then reduce to a value: join into text, count, "any exists", first or last. This builds expressions such as…answer.valueCoding.code.intersect(('43633'|'43644')).join(', ')or…answer.valueInteger.where($this > 5).count()without typing.
On an empty value field the builder first asks what the expression should produce (Number, Yes / No condition, or Answers → value). Press Apply to drop the result into the field. Opening the builder on an existing expression reads it back into blocks where possible; anything it can't represent stays editable as text.
- Number — an arithmetic chain: questions, variables and numbers joined by
× ÷ + −, plus aggregate functions (count,sum,avg,min,max) over repeating answers. Use this for scoring, BMI formulas, or any derived number. - Yes / No condition — a boolean condition tree (the same editor used for
enableWhenExpression): nested AND / ANY groups with leaf comparisons and "has answer" checks. Use this when the calculated result istrue/false— for example, to compute a boolean flag from several criteria that drives a downstreamenableWhen. - Answers → value — see the pipeline description above.
Expression Gallery
On a value field, the type chooser's 📐 Choose from gallery option opens a searchable library of ready-made calculations (BMI, eGFR, PHQ-9, due date and many more) instead of building from scratch. The result is ordinary, editable FHIRPath. See Calculation gallery for how it works and what it offers.
Understanding a result (Expression Explain)
Next to a calculated value or a visibility condition, a small ✓/✗ icon appears once the expression has a result. Clicking it opens Expression Explain — a breakdown of the expression as a tree (for conditions: each AND/OR branch and leaf comparison with its own ✓/✗) so you can see exactly which part of a condition passed or failed, not just the overall outcome.
Evaluation order
Calculated fields often depend on one another (A → B → C). The runtime builds a
dependency graph from the expressions and evaluates calculated nodes in
topological order, so a chain resolves correctly no matter how the items are
arranged in the tree. Each computed value is written back into the
QuestionnaireResponse within the same pass, so downstream expressions read the
fresh value. Circular dependencies are evaluated best-effort in tree order.
Live and safe to experiment
Expressions are re-evaluated on every edit, and the preview updates immediately. An expression that fails to parse or throws is skipped rather than breaking the form, so you can iterate on a formula and watch the result change as you type. A short example of the kind of expression you might write:
%age > 18 and %gender = 'male'
Testing expressions (FHIRPath tester)
To try an expression without wiring it into an item, open ⚙ Settings ▾ → 🧪
FHIRPath tester…. A small console opens above the preview and evaluates whatever
you type against the current QuestionnaireResponse — the same runtime the
preview uses — with %resource and all questionnaire %variables in scope,
showing the result (or a parse error) live. Because it sits above the form, you
can keep editing answers with it open and watch the value change, e.g.
%resource.descendants().where(linkId='bmi').answer.valueDecimal.
Next: Extensions & SDC.