Composition & reuse
Use these blocks to embed another frame, reuse a block template, or provide context to descendant blocks. See Pages & templates for how they compose into a page.
@block/page
The root of a route’s page — a padded container with an optional title, and the place to set the page’s default time range. It’s typically the outermost block, with everything else nested inside it.
{ "@block/page": { "title": "Dashboard", "padding": "lg", "block": { "@block/text": "Body" } } }@block/frame
Renders its child using another frame’s view. The id can contain {Param} placeholders filled from params.
{ "@block/frame": { "id": "services/{ServiceName}", "params": { "ServiceName": "checkout" }, "block": { "@block/stat": { "title": "Spans", "value": { "@expr/query": "SELECT count() FROM spans" } } } }}params values are plain, decoded strings — a value containing a / or a space is escaped for you when the concrete id is built, so an operation named GET /api/products still addresses one path segment.
When you already hold the concrete id — the one in the URL, with its placeholders filled and its segments percent-encoded — reference it as instance_id instead. It is used verbatim, so a segment that already carries a %2F is not escaped a second time:
{ "@block/frame": { "instance_id": "services/checkout/operations/oteldemo.CheckoutService%2FPlaceOrder", "block": { "@block/text": "Rendered against that operation's view" } }}Use id + params when authoring by hand, instance_id when copying an id out of the address bar (or when a tool hands you one, as “copy block” and a notebook’s frame fence do). The two are mutually exclusive.
An embed is a filter boundary: it inherits the surrounding timerange, but not the surrounding filters. A filter names the tables it applies to, and those are the host view’s — the embedded frame’s view need not define them at all. Filter the embed from controls inside it.
@block/outlet
Renders the page section selected by the current route. It takes no props.
{ "@block/outlet": {} }The page content around the outlet remains visible when the route changes sections. The outlet renders nothing when no section is selected, including when the page URL has no routes.default or the page is rendered outside a route.
@block/use
Renders a named block template with parameters. Use it to reuse a block structure, such as a service row or metric tile.
{ "@block/use": { "ref": "#/block/service_row", "params": { "service_name": "checkout" } } }Add an id to name the instance. A shared template can’t declare one itself — an id is view-unique, and a template used twice would declare it twice — so the use site names it, per call:
{ "@block/use": { "ref": "#/block/service_row", "params": { "service_name": "checkout" }, "id": "checkout_row" }}@block/context
Makes context values available to its child blocks. Use extend to define values shared by controls and queries, set a time range for one subtree, or provide another frame’s view.
{ "@block/context": { "extend": { "greeting": { "@expr/literal": "hello" } }, "block": { "@block/text": { "text": { "@expr/get_context": "greeting" } } } }}The keys in extend resolve against the scope the same block opens, so a query there runs over the window that block’s timerange sets and against the frame its frame resolves — put them on one block rather than nesting a second @block/context.
{ "@block/context": { "timerange": { "from": { "value": -7, "unit": "days" } }, "extend": { "rows": { "@expr/query": "SELECT count() FROM traces" } }, "block": { "@block/text": { "text": { "@expr/handlebars": "{{rows}}" } } } }}@block/filter_context
Creates a scope for filter controls and queries. Controls such as @block/filter_bar, @block/search, and @block/field_filter write filters to the scope. Queries inside it read those filters.
The scope only bounds where filters are read and written; it doesn’t change which tables a filter applies to — that travels with the filter itself. @block/field_filter and @block/facet derive it from the view tables that actually define the field, so on a view combining tables with different columns the predicate only reaches the ones that can resolve it. Set table on the individual control to narrow it further.
{ "@block/filter_context": { "block": { "@block/text": "scoped" } } }