View management
Saved views — the columns, sort, filters, layout, and grouping persisted per workspace and record root, plus the fork-on-edit default-view model.
A saved view is the persisted configuration of a records table: which columns show, how rows sort, which filters apply, the layout type, and any grouping. It is one row per (workspace, root) pair, addressed under the owning workspace.
Where POST /v1/records/query is transient (it returns rows for a one-off filter/sort and writes nothing), a view is persisted — it is the base configuration a grid opens with, and the thing the UI's column / sort / layout / group controls write to.
What a view holds
| Field | Type | What it persists |
|---|---|---|
columns | array | Column set + order + per-column config (width, pin). |
sort | array | Persisted sort — { field, direction } entries. |
filters | array | Persisted filter rules (the typed FilterConfig model — see Filtering & sorting). |
layout_type | enum | The view type: table · kanban · calendar · gallery · chart · list · graph. Defaults to table. |
layout_config | object | Layout-specific config (per layout_type). |
display_fields | array | Fields surfaced by non-table layouts (list/calendar/kanban cards). |
group_by | array · null | The grouping field path — a discrete field for kanban, a date field for calendar; null for other layouts. Optional at create: when omitted on a kanban/calendar view it is auto-resolved and persisted (see Default grouping). |
is_default | boolean | Whether this is the default view for its root. Exactly one per (workspace, root). |
Layout types are eligibility-gated by root: kanban needs a discrete (enum / text) field to group by, calendar needs a date field — both stored in group_by. A root with neither cannot select that layout. When you omit group_by, the server fills a logical default so the view renders grouped immediately — see Default grouping.
Endpoints
All view operations are scoped under the workspace that owns them.
| Method | Path | Operation |
|---|---|---|
GET | /v1/workspaces/{workspace_id}/views | List the workspace's saved views. |
POST | /v1/workspaces/{workspace_id}/views | Create a view. |
PATCH | /v1/workspaces/{workspace_id}/views/{view_id} | Update a view's fields. |
DELETE | /v1/workspaces/{workspace_id}/views/{view_id} | Delete a view. |
See the Records views & statistics group in the sidebar for the full request/response schema of each.
Trigger any view type
Every view possibility is reachable from the create / update body — set layout_type (one of the seven) plus group_by where the layout needs it. For example, a kanban board grouped by status:
POST /v1/workspaces/{workspace_id}/views
{
"data": {
"type": "workspace-view",
"attributes": {
"name": "Pipeline by status",
"root": "companies",
"layout_type": "kanban",
"group_by": ["status"],
"columns": [{ "field": "name" }, { "field": "status" }],
"sort": [{ "field": "created_at", "direction": "desc" }]
}
}
}Swap layout_type for calendar with a date group_by (e.g. ["due_date"]), or list / graph / table with no group_by, to trigger the other layouts. The same attributes are accepted by PATCH to reconfigure an existing view.
Default grouping (auto_group)
A kanban or calendar view needs a group_by to render grouped; without one it shows a "select a field" state. You don't have to supply it — when group_by is omitted on a kanban/calendar create (or a layout switch to one), the server resolves a logical default and persists it onto the view.
auto_group (boolean, default true) controls this. It is a request-only control flag and is not stored on the view. Pass auto_group: false to leave group_by unset (the view then renders the "select a field" state). It has no effect when you pass an explicit group_by (including explicit null), or for layouts that don't group (table / list / graph). On PATCH, auto-resolution applies only when switching a non-default view to kanban/calendar that doesn't already group — it never overwrites an existing group_by, and it never applies to a default view (which rejects group_by changes; see below).
Resolution is deterministic first, model-assisted only when needed:
| Tier | Picks |
|---|---|
| 1 · Curated preset | A per-root default (e.g. invoices → status for kanban, due_date for calendar), used only when that field actually exists for the root. |
| 2 · Sole candidate | The single eligible field — an enum for kanban, a date for calendar — when the root exposes exactly one. |
| 3 · Model pick | When several eligible fields exist and no preset applies, a model selects the most meaningful one. The choice is persisted, so it resolves once per view, not per request. |
Auto-default only chooses from the enum (kanban) or date (calendar) candidates — it never auto-groups by free text. A root with no eligible field leaves group_by as null (the "select a field" state). The full set of eligible fields stays available for explicit selection: pass any eligible field as group_by to override the default.
The default view is a shared baseline
Exactly one view per (workspace, root) carries is_default: true. The default is a shared baseline across every user of the workspace, so its personal-state fields are protected: a PATCH to a default view that changes filters, sort, or group_by is rejected with 409 DEFAULT_VIEW_READONLY (these are exactly the fields the chat updateDataView tool would otherwise leak into the shared baseline).
Its layout fields stay mutable on the default — PATCH may still change layout_type, layout_config, display_fields, columns, name, and the is_default toggle itself.
To persist a filter, sort, or grouping, do not PATCH the default — create a new view (a fork) with those attributes. POST-ing them is always allowed; only mutating them on an existing default is blocked.
To make the fork the active view, promote it with PATCH … { "data": { "attributes": { "is_default": true } } }, which atomically demotes the previous default (the single-default invariant is enforced server-side). There is no /views/{id}/set-default action endpoint — is_default flows through the resource's PATCH like any other field (per the API Shape Standard: state transitions belong on the resource's existing PATCH, not a custom verb).
The web app automates this as fork-on-edit: the first time you change a filter/sort/group on a default grid, it creates a fork and promotes it for you. That convenience is client-side — at the API level a default simply rejects filters/sort/group_by mutations, and you fork explicitly.
How a view relates to a query
A view supplies the base columns, sort, and filters a grid opens with. POST /v1/records/query then runs against that root — you can pass a whereClause / orderBy to overlay a transient filter or sort on top of the view's base without persisting anything. To make a filter or sort stick, write it to the view via PATCH.
| Persists to the view | Transient (no view write) | |
|---|---|---|
| Filter / sort | UI controls · PATCH /views · chat /layout-style slash submenus | POST /v1/records/query · MCP well_query_records · free-text chat |
| Layout / group | UI controls · PATCH /views · chat /layout · /group slash submenus | — (no transient form; layout and group are properties of a view) |
Layout and group exist only as persisted view properties — the read-only query surfaces (/records/query, MCP, free-text chat) have no layout/group parameter. Only a surface that writes the view can change them.