Tables and charts
A Read action can answer with a table: the person reads your query's rows, and the model says what stands out.
| Author | Posts | Published |
|---|---|---|
| Sam | 12 | 75% |
| Alex | 8 | 50% |
| Robin | 5 | 100% |
- ActionTableThe rows your query returned, under the columns you declared.
- your chartThe server names the kind,
bar. Your page draws it. - the modelTold to say in a sentence or two what stands out, not to repeat the rows.
Declare columns, return rows
Implement ShowsTable on a Read action. columns() says what each row may show, and handle() returns the rows: a query, an array or a generator. Nothing outside the declared columns leaves the server, on any surface. See Show a table and Column types.
final class PostsPerAuthor extends Action implements ShowsTable
{
protected ?Effect $effect = Effect::Read;
public function columns(): array
{
return [
Column::text('author', __('Author')),
Column::integer('posts', __('Posts')),
Column::percent('published', __('Published')),
];
}
}One table, two readers
In a copilot turn, the rows split.
- handle()returns rows: a query, an array, a generator
- columns()each row keeps only the declared keys
- to the personEvery row, as a table
- a
data-viewpart after the call's row, up to 500 rows <ActionTable>draws it, with its time and Refresh- your chart:
bar,line,metricornone
- a
- to the modelA short copy
The person now sees this as a table of 3 rows.
- the first 20 rows, text cut to 80 characters
- the columns' keys, labels and descriptions
Say in a sentence or two what stands out; do not repeat the rows.
Everywhere else, a route and MCP get the same table as JSON, and actions:run prints it as a table. See What each surface gets.
The chart follows the shape
The package draws no chart. It names one in table.chart, from the columns and the number of rows: one row of numbers is a metric, a date column then numbers is a line, and a text or yes-or-no column then numbers is a bar of up to 50 rows. One chart shows one unit, so the percentage above stays out of the bar chart of posts. Your page draws it with your own components. See The chart.
Kept and refreshed
A table is kept with a stored conversation, so a reload shows the rows the model described. When the action is exposed on the web, Refresh runs it again as the person, with the same input, through every check its route gets. Another person or another tenant gets a 404. See Refresh.
Datasets: the agent asks
A dataset lets the model ask its own question within names you declare: dimensions to group and filter by, and measures to compute. The package writes the query, scoped to the tenant the call runs in. The model never writes SQL.
- The model picks namesmeasures, by, since
- Only declared nameschecked before any queryunknown: refused
- A scoped querythe tenant, global scopes, then scope()
- A tablewith a caption of what was asked
{"measures": ["posts"], "by": ["author"], "since": "-1m", "compare": true}See Datasets, and the security notes on tables and datasets.