Skip to content

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/case branch, an @expr/state / @expr/local_state / @expr/url_state defaults, an @expr/literal payload. A size prop rejects { "@expr/case": { "kind": { "big": "enormous" } } } offline, because enormous is 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’s value takes 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

Next

  • Blocks — the @block/* components expressions feed.
  • Views — the data the queries run against.
  • Expression reference — every expression type with its inputs and output.