Blocks
Inline data displays and dashboards in chat
Blocks are inline UI widgets that display live data in your workspace. They come in two flavors:
- Static blocks — declarative JSX, no imports, automatically laid out across four render sizes. This is the primary and recommended approach.
- Dynamic blocks — full React with hooks and state, rendered in a sandboxed iframe. Experimental.
Static blocks
Static blocks are self-closing JSX tags. No imports, no functions, no hooks. The system parses the JSX, extracts props, fetches data, and handles all layout and sizing automatically.
Users create them in chat with a block code fence. Apps can also suggest them
at install time via suggestedFlowsAndBlocks:
Zones
Each component is automatically placed into a layout zone:
| Zone | Components | Renders as |
|---|---|---|
| header | Status | status indicator in the header row |
| stats | Number, Currency, Percent, Ratio, Duration | metric cards side by side |
| charts | Chart | line/area/bar charts |
| data | List, Table, Image | lists, tables, and images |
| actions | Button | horizontal row of buttons |
| inputs | Choice, Confirm, Input, Form, Select | interactive user input |
Zones are laid out top-to-bottom with dividers between them. The system adapts to four render sizes:
| Size | Context | Behavior |
|---|---|---|
xs | HUD bar, tab chips | Compact: single number, dot, tiny chart |
sm | Small hover preview | Labels, small charts, truncated lists |
md | Medium floating pane | Mid-size charts, partial lists |
lg | Large full view | Full titles, axes, table rows, all items |
At xs and sm sizes, blocks with no data are automatically hidden.
The query prop
Every block component accepts a query prop to fetch data. It supports
three forms:
The string shorthand is the most concise. If the string contains :, it’s
parsed as filters; otherwise it’s treated as a key lookup.
Query fields:
| Field | Type | Description |
|---|---|---|
key | string | Filter by data key |
type | string | Filter by data type |
app | string | Filter by app id |
category | string | Filter by category |
kind | string | Filter by kind |
status | string | Filter by status |
limit | number | Max rows to return (default 30) |
sort | string | 'newest' or 'oldest' |
The extract prop
When data rows store JSON in their value field, use extract to pull out
a nested property using dot-notation:
The from prop
As an alternative to query, any block component accepts a from prop that
runs an app action at render time to get live data. Instead of reading from
the data store, the block calls the action and displays whatever it returns.
The value is a slash-command string (/appId actionName). The action runs
on every render, so from is best for data that should always be fresh.
Use query when you want to display data that has already been synced to the
data store.
Components
Metric components
Five semantic components for displaying single values. Each sets the appropriate format automatically:
| Tag | Use for | Example output |
|---|---|---|
Number | counts, integers | 42, 1,234 |
Currency | money (auto-formatted) | $1,234.56 |
Percent | percentages | 42% |
Ratio | fractions | 3/5 |
Duration | time | 2h 30m |
| Prop | Type | Description |
|---|---|---|
query | string | Data query (see above) |
extract | string | Path to extract from JSON value |
value | mixed | Static value (skips query) |
label | string | Display label |
format | string | currency, number, percent, compact, duration |
prefix | string | Text before the value |
suffix | string | Text after the value |
currency | string | Currency code (for format="currency") |
precision | number | Decimal places |
compact | boolean | Force compact number formatting |
delta | string | Change indicator text |
trend | string | up or down — colors the delta |
thresholds | object | { warn, danger, success } numeric thresholds |
trendDirection | string | positive-up or positive-down |
thresholds applies a color theme based on the numeric value: danger (red)
takes priority, then warn (yellow), then success (green).
Chart
Line, bar, or area charts with automatic axis scaling.
| Prop | Type | Description |
|---|---|---|
query | string | Data query (used at lg size) |
previewQuery | string | Key lookup for xs/sm/md sparkline preview (see below) |
extract | string | string[] | Path to extract numbers; array enables multi-series |
data | number[] | Static data (skips query) |
chartType | string | line, bar, or area |
labels | string | Extract path for x-axis labels |
label | string | Chart title |
rightScale | string[] | Series names to render on the right Y-axis (dual-axis charts) |
height | number | Custom chart height in pixels |
format | string | Y-axis value formatting: currency, number, percent |
At xs/sm/md sizes, Chart renders as a compact sparkline using
previewQuery (falls back to query if not set). At lg size, it renders
as a full interactive chart with axes and crosshair.
previewQuery best practice: For series data, use a plain key string
(e.g. previewQuery="stripe-daily-revenue") that loads the single aggregate
row. series() automatically maintains a rolling points array (up to 30
values) on aggregate rows, and Chart auto-discovers this for sparkline
rendering. This avoids expensive filter queries at small sizes.
When multiple Chart blocks share the same query.app and labels path,
they automatically combine into a single multi-series chart with a legend.
When multiple bar series exist, grouped bar mode is auto-enabled. Line and area charts with only a single data point auto-convert to bar charts.
Status
A colored health indicator dot. Renders in the header zone.
| Prop | Type | Description |
|---|---|---|
query | string | Data query |
extract | string | Path to extract status string |
value | string | Static status (skips query) |
label | string | Display label |
Status color mapping:
- Green:
up,healthy,ok - Red:
down,degraded,error - Yellow: anything else
List
Bullet or numbered list of items. Supports both simple string lists and rich object rows with auto-detected field types.
When the query returns objects and no extract is set, List renders rich rows
with auto-detected fields — titles, status badges, dates, and numbers:
Field detection uses key names: title/name → primary text,
status/state → colored badge, author/user → avatar + name,
createdAt/date → relative time, amount/count → formatted number,
additions/deletions → diff stats. Use fields or columns to override.
For explicit column control, use the columns prop with typed shorthand:
| Prop | Type | Description |
|---|---|---|
query | string | Data query |
extract | string | Path to extract per item (omit for rich rows) |
data | any[] | Static items (skips query) |
fields | string[] | Explicit field selection (auto-detected if omitted) |
columns | string | Typed field shorthand like Table ("key:type") |
ordered | boolean | Numbered list (default: bullet) |
limit | number | Max items to show |
label | string | List title |
At xs size, shows the item count. At sm, shows title + badge. At md,
shows title, badge, and truncated fields. At lg, full rows with title,
description, date, number, and badge.
Table
Tabular data with column headers, row striping, and typed cell rendering.
For full control over labels and formatting, use column definition objects:
| Prop | Type | Description |
|---|---|---|
query | string | Data query |
extract | string | Path to extract row objects |
columns | string | (string | object)[] | Column definitions (auto-detected) |
limit | number | Max rows (default 10) |
label | string | Table title |
Column string shorthand: "key" or "key:type" where type is badge,
number, date, link, boolean, or a format like currency, percent,
compact, duration.
Column object fields:
| Field | Type | Description |
|---|---|---|
key | string | Field name in the data |
label | string | Display header (defaults to key) |
type | string | text, badge, number, date, boolean, link |
format | string | currency, number, percent, compact, duration |
width | number | Flex weight (default 1, numbers get 0.6, badges 0.8) |
Cell types are auto-detected from key names — status → badge,
amount → number, createdAt → date, url → link. Only renders at lg
size — hidden at xs, sm, and md.
Button
Interactive button that triggers an app action. On press, inserts the action string as a slash command message in the channel.
| Prop | Type | Description |
|---|---|---|
label | string | Button text |
action | string | Slash command to execute (e.g. /stripe sync) |
variant | string | primary, secondary, destructive |
confirm | string | Confirmation dialog text (omit to skip) |
Hidden at xs, sm, and md sizes. Only renders at lg size. The action
is processed by the messageFlowEffect pipeline — no new mutations needed.
Image
Displays an image with error handling and CDN optimization.
The image source can also come from a query:
| Prop | Type | Description |
|---|---|---|
src | string | Image URL (or resolve via query + extract) |
query | string | Data query to resolve src |
extract | string | Path to extract URL from query result |
alt | string | Alt text |
aspectRatio | string | 16/9, 4/3, 1/1, or auto |
caption | string | Text below image (lg size only) |
Hidden at xs size. At sm/md, renders cover-fit with 120px max height.
At lg, full width with 400px max height and optional caption. Automatically
optimizes URLs for imagekit, cloudinary, and imgix CDNs.
Data
A wrapper that runs a single query and shares the result with all child blocks. Useful when multiple blocks need the same data:
Full example
A complete dashboard block for a Stripe app:
The data these blocks display comes from your app’s backend writing to the
data table via tools.data. See Data for details.
Interactive blocks
Interactive blocks let the AI (or apps) ask the user for input inline in a message. Unlike dashboard blocks which go in code fences, interactive blocks are written directly in message content and render as interactive UI.
When the user interacts (clicks a choice, fills a field, confirms), their response is automatically sent as a new message in the thread.
Choice
Pick from a set of options. Single-select submits immediately; add multi
for multi-select with a submit button.
| Prop | Type | Description |
|---|---|---|
label | string | Question text |
options | string | Comma-separated choices |
multi | boolean | Allow selecting multiple |
Confirm
Yes/no prompt with optional description and destructive variant.
| Prop | Type | Description |
|---|---|---|
label | string | Question text (required) |
description | string | Additional context |
yes | string | Confirm button text (default “Confirm”) |
no | string | Cancel button text (default “Cancel”) |
variant | string | "default" or "destructive" |
Input
Single text field with submit button.
| Prop | Type | Description |
|---|---|---|
label | string | Field label |
name | string | Key in the response message |
placeholder | string | Placeholder text |
type | string | "text", "number", "url", "email" |
required | boolean | Prevents empty submit |
Select
Scrollable option list with optional search. Best for longer lists (7+).
| Prop | Type | Description |
|---|---|---|
label | string | Field label |
name | string | Key in the response message |
options | string | Comma-separated choices |
query | string | Block query to pull options from data |
extract | string | Field to extract from query results |
searchable | boolean | Enable search filtering |
Form
Groups multiple inputs into a single submit. Children can be Input,
Choice, or Select.
| Prop | Type | Description |
|---|---|---|
label | string | Form title |
submit | string | Submit button text (default “Submit”) |
Interactive blocks are hidden at xs and sm sizes — they’re conversational
components, not dashboard elements.
Dynamic blocks (experimental)
For rich interactive UIs beyond what static blocks can do, you can write full React components with hooks and state. These render in a sandboxed iframe instead of being statically analyzed.
See Dynamic Blocks for the full reference.