Expressions
While blocks describe how to visualize your data, expressions describe what data should be displayed - and how it should be transformed. Expressions can be queries, constants, UI state, or a combination of all three. They’re also reactive - so any change in them will flow through the system automatically, re-fetching queries and updating the blocks that have subscribed.
Expressible props
Most block properties accept either a literal or an expression — they’re expressible:
{ "title": "Checkout" }{ "title": { "@expr/context_state": "selected_service" } }The first is a constant; the second binds the title to a shared piece of page state — a slot declared in a @block/context and bound by name — that other blocks read and write. A block treats both the same way — a bare literal is simply wrapped as a constant expression — so “a value” and “an expression that produces a value” are interchangeable wherever a prop is expressible.
Interchangeable, but still typed. An expressible prop knows what it holds, and that type reaches inside the expression:
- Expressions that carry an authored value through to their result are checked against the prop’s type — every
@expr/casebranch, an@expr/state/@expr/local_state/@expr/url_statedefaults, an@expr/literalpayload. Asizeprop rejects{ "@expr/case": { "kind": { "big": "enormous" } } }offline, becauseenormousis not a size. - Expressions whose result could not fit the prop are rejected outright.
{ "title": { "@expr/query": … } }fails validation: a query yields a table, and a title is text. Where a block genuinely reads a value out of a table —@block/stat’svaluetakes the sole cell of a one-row result — the query form stays legal.
This is a best-effort check, not a proof: expressions whose result cannot be known from the definition (@expr/get_context, @expr/cel, @expr/pipeline, @expr/resolve, and the rest) are admitted everywhere, so a type error inside one still surfaces at render rather than at validation.
Editors read the same types. Typing @expr/ in a prop completes to the expressions that prop admits — 15 of them in a size, not all 34 — so the ones that would be rejected are never offered.
Querying, scoped to context
A @expr/query pulls from the frame’s view and binds to the current time range, filters, and parameters automatically. A query placeholder like {ServiceName:String} binds from the surrounding context without any manual substitution:
{ "@expr/query": { "from": "requests", "select": [{ "Count": "count()" }], "where": "ServiceName = {ServiceName:String}" }}Reactivity and state
Expressions are a separate layer — rather than values baked into a block — precisely so the page can be reactive. A @expr/query re-runs when the time range changes. Shared UI state works the same way: declare a slot once in a @block/context’s extend, then bind a control and a query to it by name with @expr/context_state. A @block/switch writes selected_service, a @block/table’s query reads the same slot, and the table updates the instant the selection changes — no wiring between them beyond the shared slot.
{ "@expr/context_state": "selected_service" }Expression categories
- Queries — pull from your data, scoped to context.
- State & context — values shared across the page that blocks read and write.
- Transforms — reshape a table or a column.
- Combinators & templates — assemble values from other values.
- Composition — instantiate reusable named expressions.
Next
- Blocks — the
@block/*components expressions feed. - Views — the data the queries run against.
- Expression reference — every expression type with its inputs and output.