Record charts

Declarative specs, aggregated data and the dashboard grid.

Raw Markdown for agents: records_charts.md. MCP: read_guide("records_charts").

A chart is a small declarative spec over one record type. The database groups and aggregates, the web app draws the result, so a chart never ships single records. Saved charts live in the Records area and can be placed on the space dashboard.

Workflow: records.types.list for the record_type_id, records.types.describe for the field keys, records.charts.data with an inline spec to check the numbers, then records.charts.create. records.charts.update replaces the whole spec and needs expected_revision.

Spec: {"title": "Revenue per month", "source": {"record_type_id": "<uuid>"}, "x": {"field": "issued", "bucket": "month"}, "measures": [{"op": "sum", "field": "total", "label": "Revenue"}], "chart": {"type": "bar"}}. source also takes view (key of a saved view as base filter), filter (the same filter tree as records.rows.query), date_field plus last_days for a rolling time window.

Dimensions: x and the optional series are {field, bucket, top}. field is a text, number, boolean, date, datetime, single_select or multi_select key, created_at or updated_at, a record relation key (groups by the title of the linked record) or relation_key.field_key for one hop to a field of the linked type. bucket is day, week, month, quarter, year or auto and only fits dates. top keeps the N largest groups and folds the rest into "other". A multi_select or many-to-many dimension counts a record once per value or link.

Measures: 1 to 4 of {op, field, label}; op is count (no field), count_distinct, sum, avg, min or max; sum, avg, min and max need a number, money or count field. Use either series or several measures, not both. Money sums ignore the currency; the result carries the warning mixed_currency when more than one currency was added up.

Chart types: kpi (no x; add "compare": "previous_period" with source.date_field and last_days for the change against the window before), line, area, bar (stacked, horizontal), donut (x and one measure) and heatmap (x, series, one measure). Example by relation with a top list: {"title": "Open invoices by supplier", "source": {"record_type_id": "<uuid>", "filter": {"conditions": [{"field": "status", "op": "eq", "value": "open"}]}}, "x": {"field": "supplier", "top": 10}, "measures": [{"op": "sum", "field": "total"}], "chart": {"type": "bar", "horizontal": true}, "sort": "value_desc"}. KPI example: {"title": "New orders", "source": {"record_type_id": "<uuid>", "date_field": "created_at", "last_days": 30}, "measures": [{"op": "count"}], "chart": {"type": "kpi"}, "compare": "previous_period"}.

Result: columns [{key, role x|series|measure, label, type, options, currency}] and rows as arrays in column order; numbers arrive as strings, dates as ISO days. truncated is true when the row limit cut the result. Agents get 200 rows by default, at most 2000 with limit.

Dashboard: records.charts.board_get returns sections of a three column grid, records.charts.board_update replaces them: {"sections": [{"id": "sales", "title": "Sales", "items": [{"record_chart_id": "<uuid>", "span": 2, "height": "sm"}]}]} with span 1 to 3 and height sm or md.

Errors: invalid_chart_spec explains what does not fit the schema, unknown_field means call records.types.describe, chart_source_missing means the record type or a field of a saved chart is gone, chart_query_timeout means narrow the filter or window or mark the grouped fields as indexed.