Table Views
Browse tables in Table mode or edit inline with the spreadsheet-style Data Table mode.
Table Views
The CMS provides two view modes for every table: Table mode for browsing and navigating records, and Data Table mode for spreadsheet-style inline editing. Switch between them with the toolbar toggle.
Table Mode
Table mode is the default browse experience:
- Click rows to navigate to the record detail view
- Row action menus (three-dot icon) with view, edit, delete, and custom actions
- Column headers are sortable — click to toggle ascending/descending
- Search bar for full-text search across fields allowed by the active collection or named-view query contract
- Responsive columns that adapt to available space
Each row shows the most relevant columns for the table. Foreign key columns display human-readable labels (resolved via _label fields) instead of raw UUIDs. Boolean columns render as colored Yes/No badges. Enum columns use color-coded pills.
Row Actions
The three-dot menu on each row provides:
| Action | Visibility | Description |
|---|---|---|
| View details | Always | Navigate to the record detail page |
| Edit | When role has update access | Navigate to the edit form |
| Custom actions | Filtered by role + record state | Run actions like approve, void, post |
| Delete | When role has delete access | Delete with confirmation |
Actions are filtered by the current role's write permissions and the action's access conditions. For example, an "approve" action that requires status === "pending" only appears on pending records.
Data Table Mode
Data Table mode provides an Excel/Google Sheets-like editing experience:
- Cell selection with a blue focus ring
- Keyboard navigation using arrow keys, Tab, Enter, and Escape
- Type-to-edit — start typing to enter edit mode on the selected cell
- Row numbers displayed in the leftmost column
- Hint bar at the bottom showing keyboard shortcuts
Editable Fields
Which cells are editable is determined by the table's guards:
- Fields in
guards.updatableare editable - Fields in
guards.immutableare read-only (shown with a disabled style) - Fields in
guards.protectedare read-only (marked "Updated via actions only") - Audit fields (
createdAt,createdBy,modifiedAt,modifiedBy) are always read-only
Non-editable cells can still be selected and copied, but they don't enter edit mode.
Cell Types
| Column Type | Edit Control | Behavior |
|---|---|---|
| Text | Text input | Free-text entry |
| Number | Number input | Numeric entry with step controls |
| Boolean | Yes/No dropdown | Toggle between Yes and No |
| FK reference | Typeahead dropdown | Server-side search with debounced queries |
| Enum | Select dropdown | Choose from allowed values |
Saving
Changes are auto-saved when you:
- Press Enter to confirm and move down
- Press Tab to confirm and move to the next editable cell
- Click away from the editing cell (blur)
Press Escape to cancel an edit and revert to the previous value.
Views
Views are named column projections defined in your table's resource config. They control which columns appear in the table based on the current role.
Toolbar
The toolbar shows a view dropdown when the table has named views. Its options are the named views the current role may access.
For a table with no named views, the toolbar renders no view control and the
table uses its ordinary collection projection. Declaring any read.views
replaces that projection with the named-view contract; All Fields is not an
extra option alongside the named views. Set read.defaultView to choose which
named projection opens first in the CMS. The schema registry carries that
selection as defaultView, and the CMS calls the named-view route from its
first request.
If named views exist without read.defaultView, the registry emits
requiresExplicitView: true. The CMS shows Choose a view and makes no data
request until the user selects an accessible named view. It never silently
chooses the first accessible projection, because projection choice is part of
the table's data/access contract.
Views are filtered by role access. If a view's access rules require admin and the current user is a member, that view won't appear in the dropdown.
Example
Given this definition:
read: {
defaultView: 'summary',
views: {
summary: {
fields: ['id', 'name', 'status'],
access: { roles: ['member+'] },
},
full: {
fields: ['id', 'name', 'status', 'ssn', 'internalNotes'],
access: { roles: ['admin+'] },
},
},
}- Members see
summary, which opens by default. - Admins and owners see
summaryandfull;summarystill opens by default.
When a view is selected, the CMS calls
GET /api/v1/<table>/views/<name>, fetching only the projected columns. With
named views but no read.defaultView, bare GET /api/v1/<table> returns
400 VIEW_REQUIRED. That can be intentional for API callers that must choose a
projection, but a CMS table intended to open directly should declare an
accessible default view.
Views Used by FK Lookups
When this table is the target of an FK selector, a table with no named views
uses its ordinary collection route. A table with named views is different: the
CMS lookup uses only the authored read.defaultView, verifies that the current
role may access it, and calls its named-view route. It never retries the bare
collection endpoint and never chooses another named view automatically.
Make that default view lookup-safe by including the table's primary key and
displayColumn in fields. For searchable label typeahead, include the
displayColumn in the view's effective query.searchable allowlist. An
explicit named-view allowlist is authoritative, so the column does not also
need .searchable(). The schema registry emits the effective inherited view
access and searchability so the CMS can decide before requesting data.
If the default is missing, inaccessible, or omits the primary key/label, the
selector is disabled with an actionable message. If the default is otherwise
valid but its effective searchable list is empty, the selector shows the first
50 options as a chooser, keeps search read-only, and sends no ?search.
Default Sort
You can set a default sort order for a table by adding defaultSort to your resource config:
export default defineTable(podcastEpisodes, {
defaultSort: { field: "createdAt", order: "desc" },
// ...
});When the CMS loads the table, it applies this sort automatically. Users can still click column headers to change the sort — defaultSort only sets the initial state.
defaultSort does not select a named view. Adding or changing it cannot resolve
VIEW_REQUIRED; use read.defaultView (or call the explicit named-view route)
for that.
Pagination
The bottom of every table view shows pagination controls:
- Page range indicator — "Showing 1-25 of 142"
- Page buttons — Navigate directly to a page, with ellipsis for large page counts
- Previous/Next arrows
The default page size is 25 records. The CMS resets to page 1 when you change the search query or switch views.
Search
The search bar performs full-text search across the fields allowed by the
active collection or named-view query contract. Type a query and the CMS
debounces the request, then fetches matching records from the API. For named
views, the registry's effective query.searchable list tells the CMS whether
the route accepts ?search and which projected fields participate.
Search works in both Table and Data Table modes when the active route supports
it. For FK selectors, an empty named-default-view search allowlist activates
the first-50 chooser behavior above, which sends no ?search.
Next Steps
- Inline Editing — Deep dive into spreadsheet editing, FK typeahead, and keyboard shortcuts
- Security — How roles and guards affect the table UI
- Actions — Custom actions in the row menu and detail view