Composition and orchestration
FUME evaluates expressions and FLASH blocks together.
- FLASH expresses where to write in the FHIR output (FHIR target paths).
- JSONata-based expressions compute what to write (values, control-flow, orchestration).
This page focuses on how the FUME expression layer and FLASH blocks compose.
The same expression layer also supports mappings that return direct values or custom JSON structures. This page stays focused on the places where those expressions compose with FLASH.
If you're looking for rule forms and indentation, see Syntax. If you're looking for how fan-out and append/override works, see Evaluation model.
Mental model: one language, two layers
Use this as the default "combined" mental model:
- FLASH is the structural layer: it decides where to write in the output (FHIR paths, array fan-out, append vs override).
- JSONata-based expressions are the expression layer: they decide what values to write (transforms, lookups, control flow, orchestration).
In practice, authors combine them: FLASH rules use JSONata on the right-hand side, and orchestration blocks can wrap multiple FLASH blocks inside (...).
Mapping recipe (end-to-end)
Most real mappings follow the same shape:
- Input normalization: XML/CSV/HL7v2/etc are normalized to JSON (so your expressions operate on predictable JSON structures).
- Orchestration (JSONata): bind ids, compute intermediate values, optionally call other saved mappings, optionally fetch/enrich from a FHIR server.
- Build resources (FLASH): write FHIR-shaped output using paths + nested context rules.
- Terminology/validation: use terminology helpers (ConceptMaps/ValueSets) and let definition-driven shaping/validation emit diagnostics.
- Diagnostics and tracing: keep
$trace(...)breadcrumbs so verbose evaluation is explainable.
When debugging, ask: "Is this a structure problem (FLASH path/context) or a value problem (JSONata expression)?"
Where JSONata appears
Right-hand side of =
The right-hand side of an assignment rule is an expression evaluated against the current input context.
InstanceOf: Patient
* active = true
* birthDate = dateOfBirth
* name.text = firstName & " " & lastName
Instance:
Instance: is an expression used to compute the resource id.
Instance: $uuid("ExampleCare-Patient-001")
InstanceOf: Patient
* active = true
Input-context rules: (<input-expr>).<path>
Inside an input-context rule, the part in parentheses is JSONata. It is evaluated first, and if it produces an array, the nested rule set evaluates once per item.
InstanceOf: Patient
* (phones).telecom
* system = "phone"
* value = value
Variable bindings inside a FLASH block
Within a FLASH block you can bind variables using := and then use them in later rules.
InstanceOf: Patient
$fullName := firstName & " " & lastName
* name.text = $fullName
JSONata orchestration around FLASH
A complete FUME expression can be a single FLASH block, or a broader orchestration that mixes FLASH with direct expression results and returns a resource, an array, a custom JSON object, or a Bundle.
Two common building blocks are:
- Parenthesized blocks
(...)to sequence expressions - Semicolons
;to separate expressions inside a block
In JSONata, a parenthesized block evaluates each expression in order and returns the value of the final expression.
Pattern: build several resources and return an array
(
$patientId := $uuid("ExampleCare-Patient-001");
$patient := (
Instance: $patientId
InstanceOf: Patient
* active = true
* name
* family = "ExampleCare"
* given = "Avery"
);
$encounter := (
InstanceOf: Encounter
* status = "finished"
* subject.reference = "Patient/" & $patientId
);
[$patient, $encounter]
)
Gotchas that show up in orchestration:
- If a FLASH block produces no writes, it can evaluate to
undefined. When you later build arrays/objects in JSONata,undefinedvalues are often dropped. $warn()and$info()returnundefined. They're useful as standalone statements, but if you put them inside an array/object constructor, they do not contribute a value.
Pattern: compose into a Bundle
This pattern keeps resource construction in FLASH, then assembles a collection Bundle that embeds those resources.
(
$patientId := $uuid("ExampleCare-Patient-001");
$patient := (
Instance: $patientId
InstanceOf: Patient
* active = true
* name
* family = "ExampleCare"
* given = "Avery"
);
$encounter := (
InstanceOf: Encounter
* status = "finished"
* subject.reference = "Patient/" & $patientId
);
(
InstanceOf: Bundle
* type = "collection"
* entry
* resource = $patient
* entry
* resource = $encounter
)
)
Variables and scoping
Variable bindings use :=:
(
$x := 1;
$y := $x + 1;
$y
)
Practical scoping rules for combined FUME mappings:
- A variable is visible after it is bound.
- Variables are scoped to the smallest enclosing JSONata block
(...)(and any nested blocks can read outer variables). - A useful pattern is "one orchestration block" that binds shared ids and intermediate resources, then returns either a final resource, a Bundle, or an array.
If you want two FLASH blocks to share variables, place them under the same outer orchestration block.
Mapping calls (mapping repository functions)
When you run a saved mapping (for example via the server's saved-mapping endpoints), the runtime can expose other saved mappings as callable JSONata functions.
For the reader-facing server endpoint model behind those saved-mapping calls, see Server API execution patterns.
Conceptually:
- A saved mapping named
normalizePatientis available as a function call:$normalizePatient(...). - The function evaluates the referenced mapping using the same FUME runtime.
Calling convention
Mapping calls accept up to two arguments:
input(optional): the input value for the called mapping. When omitted, the called mapping uses the current input context.bindings(optional): an object whose keys become variables inside the called mapping.
Example:
(
$patientId := $uuid("ExampleCare-Patient-001");
$patient := $normalizePatient($, {"patientId": $patientId});
(
InstanceOf: Bundle
* type = "collection"
* entry
* resource = $patient
)
)
Inside normalizePatient, the binding can be referenced as $patientId.
Passing bindings to control behavior
Bindings are useful for configuration-like inputs that you don't want to mix into the main source document.
Typical uses:
- deterministic ids and reference wiring (
$patientId,$encounterId) - reusable code systems/URIs (
$mrnSystem) - feature flags for shaping output (
$includeNarrative)
Tracing and logging helpers
Orchestration often benefits from "breadcrumb" diagnostics.
$warn(message)emits a warning diagnostic and returnsundefined.$info(message)emits an info diagnostic and returnsundefined.$trace(value?, label, projection?)emits a trace diagnostic and returnsvalue(so it can be used inline).
Examples:
(
$info("Starting ExampleCare patient transform");
$patientId := $uuid("ExampleCare-Patient-001");
$patient := (
Instance: $patientId
InstanceOf: Patient
* active = true
);
$trace($patientId, "patientId");
$patient
)
These helpers surface in verbose evaluation output and are also subject to the same threshold policies described in Diagnostics.
For the detailed function signatures, see:
Mixed examples
These are intentionally small snippets that show where JSONata shows up inside FLASH, and how JSONata can wrap FLASH.
Example 1 - Deterministic ids + terminology translate + tracing
This example combines:
- deterministic
$uuid(seed)for stable ids $translateCode(...)to translate an encounter status via a ConceptMap$trace(...)to record intermediate values without breaking expression flow
This example uses the EncounterStatusCanonicalMap ConceptMap from FHIR R4.
(
$patientId := $uuid(patient.patientId);
$resourceStatus := $translateCode(encounter.status, "sc-encounter-status");
$trace($patientId, "patientId");
$trace($resourceStatus, "translatedEncounterStatus");
(
Instance: $patientId
InstanceOf: Patient
* active = true
* name.text = patient.name.display
* birthDate = patient.birthDate
)
)
Function refs:
Example 2 - Input-context rules change what fields are addressable
Inside an input-context rule, the expression in parentheses evaluates first, and then the nested block runs once per produced item.
InstanceOf: ExplanationOfBenefit
* (claimLines).item
* sequence = lineNumber
* productOrService.text = serviceDisplay
* adjudication
* amount.value = allowedAmount
Notice how lineNumber, serviceDisplay, and allowedAmount are now directly addressable inside the nested block: the input context is "one claim line at a time".
Example 3 - $zip for pairing parallel arrays
Sometimes your input gives parallel arrays rather than an array of objects. $zip pairs them so you can iterate over tuples.
$zip(["PROC-010", "PROC-020"], [110, 300]).{
"serviceCode": $[0],
"allowedAmount": $[1]
}
Function ref:
Example 4 - Enrichment with $search (FHIR server)
FHIR server helpers run only when a FHIR server is configured (server URL/credentials/etc). A common pattern is "query in JSONata, write results in FLASH".
(
$patientReference := $literal("Patient?identifier=urn:examplecare:member-id|" & memberId);
(
InstanceOf: Observation
* status = "final"
* subject.reference = $patientReference
)
)
Function ref: