Combinators & templates
Combinators glue values together — chain a transform pipeline, pick a value by case, merge several reactive inputs, or build up an object. They’re the plumbing you reach for when a single query or state key isn’t quite the shape a block wants. See the expression reference for the exact inputs.
@expr/pipeline
The backbone of client-side data shaping: a source expression followed by a sequence of transform operators, each receiving the previous result. Reach for it whenever you’d otherwise nest transforms inside transforms — it reads top-to-bottom instead of inside-out.
{ "@expr/pipeline": [ { "@expr/query": "SELECT StatusCode, Duration FROM spans" }, { "@expr/filter": { "where": "StatusCode > 0" } }, { "@expr/derive": { "duration_ms": "Duration / 1000000" } } ]}@expr/case
Picks a value by matching a context value against a set of cases (with a default) — a lookup table. Use it to map a status to a colour, a mode to a label, an enum to a human string, without a chain of conditionals.
{ "@expr/case": { "status": { "ok": "green", "error": "red", "default": "gray" } } }@expr/combine_latest and @expr/concat
Merge several reactive inputs. @expr/combine_latest emits an array of the latest values of its inputs, re-emitting whenever any one changes — use it to feed a block something that depends on several state keys or queries at once. @expr/concat flattens several array-valued expressions into one list — handy for stitching together option lists or rows from more than one source.
{ "@expr/combine_latest": [{ "@expr/get_context": "userName" }, { "@expr/get_context": "userRole" }]}@expr/object, @expr/spread and @expr/get
Small value plumbing. @expr/get reads a value out of context and walks a dot path into it ("totals.TotalCost") — pull a single field out of a one-row query, or a nested property out of a state object.
{ "@expr/get": "totals.TotalCost" }@expr/object builds a one-property object from a { key, value } pair. Both positions take an expression, so the key can be computed too.
{ "@expr/object": { "key": "env", "value": { "@expr/get_context": "env" } } }{ "@expr/object": { "key": { "@expr/get_context": "field" }, "value": 1 } }A many-property object is a merge of pairs, which is what @expr/spread is for.
@expr/spread merges objects left to right — literal ones, expressions resolving to one, or @expr/object pairs — so a later part’s keys win. Reach for it to assemble a map from pieces computed separately: a base set of values plus a conditional override.
{ "@expr/spread": [ { "service": "checkout" }, { "@expr/object": { "key": "env", "value": { "@expr/get_context": "env" } } } ]}Both are checked against the position they sit in: where a slot’s type says what its values are, each value is validated against it rather than accepted as anything.
@expr/resolve
Deep-substitutes any embedded @expr/* expressions inside a JSON-like value, leaving the surrounding shape intact. Reach for it to template a structured object — say, a block’s config — where only some fields are dynamic.
{ "@expr/resolve": { "title": "Dashboard", "user": { "@expr/get_context": "userName" }, "tags": ["static", { "@expr/get_context": "currentTag" }] }}