Docyrus Data View Config Design
A data view is a saved, named configuration over a single data source's records. It does not change the schema or the data — it controls how a grid/gallery surface (TanStack Table + react-querybuilder) presents that data source: which columns show, in what order, how rows are filtered/sorted/grouped/colored, and how paging/inline-editing/forms behave. Views back the "tab strip" / view picker in Docyrus list pages (useDocyrusDataViewSelect).
Author views with docyrus studio *-data-view, then validate (read back) and test (confirm the saved JSON unpacks to the intended grid state). An unvalidated view is not done.
For the schema being viewed see docyrus-data-source-design; for the React grid that renders views see docyrus-data-grid-page-design; for full CLI flag reference see docyrus-cli-app.
Critical model: opaque JSON blobs with packed sub-keys
The backend stores a view as a flat row (tenant_app_config_data_view) with four opaque jsonb columns — columns, filters, sort, color_rules — plus scalar columns (name, description, icon, color, is_default, sort_order, quick_filter_fields, archived, tenant_app_id). The backend does not validate the inner shape of the four jsonb blobs (the DTO only checks they are objects). The grid UI packs/unpacks them with specific sub-keys — if you write the wrong sub-keys the view saves fine but renders as empty defaults.
The single most important fact: the stored sub-key names differ from the TanStack/UI property names. e.g. visible-columns is stored as columns.visibility (not columnVisibility); color rules are color_rules.row / color_rules.cell (not rowColorRules). Get these exact key names from references/view-config-fields.md — do not guess them.
columns → { visibility, order, pinning, grouping, rowHeight, displayMode,
pagingEnabled, pagingMode, pageSize, inlineEditingEnabled,
readOnlyColumns, columnOptions, formId }
filters → { columnFilters, filterQuery }
sort → { sorting }
color_rules → { row, cell }
Workflow
Confirm auth, app, and data source. A view belongs to a data source inside an app.
docyrus auth who --json
docyrus apps list --json # find appSlug
docyrus studio list-fields --appSlug <app> --dataSourceSlug <ds> --json
The list-fields output gives the column ids / field slugs you reference in visibility, order, grouping, sorting, filters, and color rules. For enum/select/status filter values you need enum row UUIDs — docyrus studio list-enums --appSlug <app> --dataSourceSlug <ds> --fieldSlug <f> --json. If there is no session, stop and ask the user to run docyrus auth login.
Decide the view's intent. Per view settle: name (tab label), which columns are visible and in what order, default sort, any filter (simple column filters and/or a query), grouping, row height, display mode (table or gallery), color rules, paging, inline editing, whether it is the default view. Sketch this for the user when ambiguous. If the data source has a status/single-select field, default to status-based views — see Status fields → design views per status.
Build the payload. Prefer --from-file ./view.json for anything beyond a trivial view — the blobs are nested JSON and awkward inline. Use the per-blob convenience flags (--columns, --filters, --sort, --colorRules, --quickFilterFields take JSON strings; --name, --description, --icon, --color, --isDefault, --sortOrder are scalars) only for small views. See references/view-config-fields.md for every key's exact name, type, and accepted values, and references/workflow-examples.md for ready-to-adapt payloads.
Create the view. See Create.
Validate. Re-read with get-data-view and confirm every blob has the intended sub-keys with the right column ids / enum UUIDs. See Validate.
Test. Confirm the saved JSON round-trips to the grid state you intended (and, if possible, that it filters/sorts real records correctly). See Test.
Status fields → design views per status
A status field (status / single-select / "stage"-style enum) is a strong signal to design status-based views. The existence of such a field means the records move through stages, and the most useful tab strip on a list page is one that splits records by stage. When a data source has a status field, default to creating a set of status views (plus a default "All" view) unless the user wants otherwise.
How many views depends on how many enum options the status field has:
- 3–4 options → one view per option. e.g. status
New, Active, Won, Lost → four views New / Active / Won / Lost, each filtering status = <enum uuid>.
- More than ~4 options → group related statuses into fewer, meaningful views. One tab per value becomes an unusable strip. Group them by stage of life, e.g.
New, In Progress, On Hold → an "Open" view; Done, Cancelled → a "Closed" view. Pick group names the user's domain would recognise.
- Usually also keep an "All" view (no status filter) and mark the most-used view (often "All" or "Open") as
is_default.
Each status view is an ordinary view whose filters blob targets the status field by enum row UUID, never the label (read UUIDs from docyrus studio list-enums ... --fieldSlug status --json):
- Single status: one
filters.filterQuery rule with = (or a columnFilters entry).
- Grouped (multi-status) view: a
filters.filterQuery rule using in with an array of UUIDs.
Grouped "Open" view payload (filter only — merge with the rest of your view config):
{
"name": "Open",
"icon": "circle-dot",
"sort_order": 2,
"filters": {
"columnFilters": [],
"filterQuery": {
"combinator": "and",
"rules": [
{ "field": "status", "operator": "in",
"value": ["<new-uuid>", "<in-progress-uuid>", "<on-hold-uuid>"] }
]
}
}
}
These status views are exactly what backs the view-type / status tab strip planned during project planning — keep one view per visible tab.
Studio data-view command cheat-sheet
Selectors — pass exactly one of each pair; the CLI resolves the other and routes to /v1/apps/<appSlug>/data-sources/<dataSourceSlug>/views:
--appId | --appSlug, --dataSourceId | --dataSourceSlug. Mutations also accept --data '<json>' or --from-file ./view.json (JSON only); explicit per-blob flags are merged over the file/data payload. Append --json for machine output.
docyrus studio list-data-views --appSlug crm --dataSourceSlug contacts --json
docyrus studio get-data-view --appSlug crm --dataSourceSlug contacts --viewId <id> --json
docyrus studio create-data-view --appSlug crm --dataSourceSlug contacts --from-file ./view.json --json
docyrus studio update-data-view --appSlug crm --dataSourceSlug contacts --viewId <id> --from-file ./view.json --json
docyrus studio delete-data-view --appSlug crm --dataSourceSlug contacts --viewId <id> --json
list-data-views / create-data-view accept an optional --tenantAppId (maps to tenant_app_id) to scope a view to a different app than the data source's owner. Most views omit it.
update-data-view additionally accepts --archived (soft-delete / hide from the tab strip). archived is not the same as deleting — prefer --archived true over delete-data-view when you just want to retire a view.
create-data-view requires name. Everything else is optional; an omitted blob means "grid defaults" for that aspect.
Create a data view
Minimal (just a named view — inherits all grid defaults):
docyrus studio create-data-view --appSlug crm --dataSourceSlug contacts \
--name "All Contacts" --isDefault true --sortOrder 1 --json
Configured (recommended via file — see references/workflow-examples.md for full examples):
docyrus studio create-data-view --appSlug crm --dataSourceSlug contacts \
--from-file ./active-leads-view.json --json
Critical rules
- Stored sub-keys ≠ UI property names. Visible columns →
columns.visibility, order → columns.order, pinning → columns.pinning, grouping → columns.grouping, color rules → color_rules.row / color_rules.cell, sort → sort.sorting, filters → filters.columnFilters / filters.filterQuery. Wrong key = silently ignored (view renders as defaults). Full list in references/view-config-fields.md.
- Blobs are not validated server-side. The DTO only asserts each of
columns/filters/sort/color_rules is an object. Malformed inner JSON is accepted and then ignored by the UI — so always validate by reading back and checking the unpacked shape; the create call succeeding proves nothing about correctness.
- Column ids are field slugs (plus built-ins like
id, name, autonumber_id). Get them from list-fields. A typo'd column id in visibility/order/sorting is dropped silently.
- Enum/relation filter values are UUIDs, never labels (same rule as records). Read them from
list-enums / the related data source before writing a filters blob that targets a select/status/relation field.
columns.visibility is an allow/deny map, not a column list. It is { "<colId>": true|false }; columns absent from the map use their default visibility. To hide a column set it false; to force-show set true. Use columns.order to control sequence.
- Many features live inside the
columns blob, not their own column: paging (pagingEnabled/pagingMode/pageSize), inline editing (inlineEditingEnabled/readOnlyColumns), per-column options (columnOptions), and the bound form (formId). This is so the backend needs no schema change — see references/view-config-fields.md.
is_default is per data source. Marking a view default does not auto-unset other views server-side; if exactly-one-default matters, clear the previous default's is_default yourself. The UI fallback chain is: persisted localStorage selection → is_default view → first view.
quick_filter_fields is a top-level uuid[] column (field ids), not part of any blob.
- Validate then test, every time. Read back the view and confirm the unpacked shape; delete throwaway test views you create.
Validate
After authoring, confirm the saved view is exactly what you intended:
docyrus studio get-data-view --appSlug <app> --dataSourceSlug <ds> --viewId <id> --json — confirm scalars (name, is_default, sort_order, icon, color, archived) and that each blob holds the expected sub-keys with valid column ids / enum UUIDs.
- Cross-check every column id in
columns.visibility/order/grouping, every sort.sorting[].id, every filter field, and every color_rules formula reference against list-fields output — drop anything that does not resolve.
docyrus studio list-data-views ... --json — confirm the view appears, ordering (sort_order), and that at most one is_default is set if that was the intent.
Per-field "what correct looks like" checklist is in references/workflow-examples.md.
Test
Prove the view behaves as intended:
- Round-trip check — re-read the view and unpack each blob using the key map in references/view-config-fields.md; confirm it yields the visible columns / order / sort / filter you designed.
- Filter sanity — run the equivalent query against real records to confirm the filter selects the right rows, e.g.
docyrus ds list <app> <ds> --columns "..." --filter "..." (see docyrus-cli-app), or docyrus dsql for complex filters. The view's filters.filterQuery mirrors react-querybuilder; the column filters mirror TanStack columnFilters.
- Clean up —
docyrus studio delete-data-view ... --viewId <id> for throwaway views (or --archived true to retire without deleting).
Full test playbook is in references/workflow-examples.md.
References
- references/view-config-fields.md — The authoritative field reference: every scalar column, the exact packed sub-keys of
columns / filters / sort / color_rules, their types, accepted values (display modes, row heights, paging modes), the UI-name↔stored-key mapping, and gotchas. Read this before writing any non-trivial view payload.
- references/workflow-examples.md — End-to-end worked examples (default view, filtered+sorted+grouped view, color-coded view, gallery view, paging + inline-editing view, form-bound view) as copy-adaptable JSON payloads, plus the validation checklist and test playbook.
1---2name: docyrus-data-view-config-design3description: Design, configure, validate, and test Docyrus data views (saved grid/gallery views) with the `docyrus studio` data-view CLI commands. A data view is a named saved configuration of how one data source's records are presented — visible columns, order/pinning/grouping, row height, table/gallery display, sorting, filters (column filters + react-querybuilder query), quick-filter fields, row/cell color rules, paging, inline editing, and a bound form. Use when creating or editing a saved view / tab / list view, hiding or reordering columns, setting a default filtered/sorted view, grouping rows, adding color rules, configuring paging or inline editing, or binding a form to a view. Triggers on "create a data view", "saved view", "add a grid tab", "default view", "filter/sort a view", "hide columns", "group by", "color rules", "gallery view", `docyrus studio create-data-view`, `update-data-view`, `list-data-views`. For the schema use docyrus-data-source-design; for the React grid UI use docyrus-data-grid-page-design.4---5
6# Docyrus Data View Config Design
7
8A **data view** is a saved, named configuration over a single data source's records. It does **not** change the schema or the data — it controls how a grid/gallery surface (TanStack Table + react-querybuilder) presents that data source: which columns show, in what order, how rows are filtered/sorted/grouped/colored, and how paging/inline-editing/forms behave. Views back the "tab strip" / view picker in Docyrus list pages (`useDocyrusDataViewSelect`).
9
10Author views with `docyrus studio *-data-view`, then **validate** (read back) and **test** (confirm the saved JSON unpacks to the intended grid state). An unvalidated view is not done.
11
12For the schema being viewed see **docyrus-data-source-design**; for the React grid that renders views see **docyrus-data-grid-page-design**; for full CLI flag reference see **docyrus-cli-app**.
13
14## Critical model: opaque JSON blobs with packed sub-keys
15
16The backend stores a view as a flat row (`tenant_app_config_data_view`) with four **opaque `jsonb` columns** — `columns`, `filters`, `sort`, `color_rules` — plus scalar columns (`name`, `description`, `icon`, `color`, `is_default`, `sort_order`, `quick_filter_fields`, `archived`, `tenant_app_id`). The backend does **not** validate the inner shape of the four jsonb blobs (the DTO only checks they are objects). The grid UI packs/unpacks them with specific sub-keys — **if you write the wrong sub-keys the view saves fine but renders as empty defaults.**
17
18The single most important fact: **the stored sub-key names differ from the TanStack/UI property names.** e.g. visible-columns is stored as `columns.visibility` (not `columnVisibility`); color rules are `color_rules.row` / `color_rules.cell` (not `rowColorRules`). Get these exact key names from [references/view-config-fields.md](references/view-config-fields.md) — do not guess them.
19
20```
21columns → { visibility, order, pinning, grouping, rowHeight, displayMode,
22 pagingEnabled, pagingMode, pageSize, inlineEditingEnabled,
23 readOnlyColumns, columnOptions, formId }
24filters → { columnFilters, filterQuery }
25sort → { sorting }
26color_rules → { row, cell }
27```
28
29## Workflow
30
311. **Confirm auth, app, and data source.** A view belongs to a data source inside an app.
32 ```bash
33 docyrus auth who --json
34 docyrus apps list --json # find appSlug
35 docyrus studio list-fields --appSlug <app> --dataSourceSlug <ds> --json
36 ```
37 The `list-fields` output gives the **column ids / field slugs** you reference in `visibility`, `order`, `grouping`, `sorting`, filters, and color rules. For enum/select/status filter values you need enum **row UUIDs** — `docyrus studio list-enums --appSlug <app> --dataSourceSlug <ds> --fieldSlug <f> --json`. If there is no session, stop and ask the user to run `docyrus auth login`.
38
392. **Decide the view's intent.** Per view settle: name (tab label), which columns are visible and in what order, default sort, any filter (simple column filters and/or a query), grouping, row height, display mode (table or gallery), color rules, paging, inline editing, whether it is the default view. Sketch this for the user when ambiguous. **If the data source has a status/single-select field, default to status-based views — see [Status fields → design views per status](#status-fields--design-views-per-status).**
40
413. **Build the payload.** Prefer `--from-file ./view.json` for anything beyond a trivial view — the blobs are nested JSON and awkward inline. Use the per-blob convenience flags (`--columns`, `--filters`, `--sort`, `--colorRules`, `--quickFilterFields` take JSON strings; `--name`, `--description`, `--icon`, `--color`, `--isDefault`, `--sortOrder` are scalars) only for small views. See [references/view-config-fields.md](references/view-config-fields.md) for every key's exact name, type, and accepted values, and [references/workflow-examples.md](references/workflow-examples.md) for ready-to-adapt payloads.
42
434. **Create the view.** See [Create](#create-a-data-view).
44
455. **Validate.** Re-read with `get-data-view` and confirm every blob has the intended sub-keys with the right column ids / enum UUIDs. See [Validate](#validate).
46
476. **Test.** Confirm the saved JSON round-trips to the grid state you intended (and, if possible, that it filters/sorts real records correctly). See [Test](#test).
48
49## Status fields → design views per status
50
51**A status field (status / single-select / "stage"-style enum) is a strong signal to design status-based views.** The existence of such a field means the records move through stages, and the most useful tab strip on a list page is one that splits records by stage. When a data source has a status field, default to creating a set of status views (plus a default "All" view) unless the user wants otherwise.
52
53How many views depends on how many enum options the status field has:
54
55- **3–4 options → one view per option.** e.g. status `New`, `Active`, `Won`, `Lost` → four views `New` / `Active` / `Won` / `Lost`, each filtering `status = <enum uuid>`.
56- **More than ~4 options → group related statuses into fewer, meaningful views.** One tab per value becomes an unusable strip. Group them by stage of life, e.g. `New`, `In Progress`, `On Hold` → an **"Open"** view; `Done`, `Cancelled` → a **"Closed"** view. Pick group names the user's domain would recognise.
57- Usually also keep an **"All"** view (no status filter) and mark the most-used view (often "All" or "Open") as `is_default`.
58
59Each status view is an ordinary view whose `filters` blob targets the status field **by enum row UUID, never the label** (read UUIDs from `docyrus studio list-enums ... --fieldSlug status --json`):
60
61- **Single status:** one `filters.filterQuery` rule with `=` (or a `columnFilters` entry).
62- **Grouped (multi-status) view:** a `filters.filterQuery` rule using `in` with an array of UUIDs.
63
64Grouped "Open" view payload (filter only — merge with the rest of your view config):
65
66```jsonc
67{
68 "name": "Open",
69 "icon": "circle-dot",
70 "sort_order": 2,
71 "filters": {
72 "columnFilters": [],
73 "filterQuery": {
74 "combinator": "and",
75 "rules": [
76 { "field": "status", "operator": "in",
77 "value": ["<new-uuid>", "<in-progress-uuid>", "<on-hold-uuid>"] }
78 ]
79 }
80 }
81}
82```
83
84These status views are exactly what backs the view-type / status tab strip planned during project planning — keep one view per visible tab.
85
86## Studio data-view command cheat-sheet
87
88Selectors — pass exactly one of each pair; the CLI resolves the other and routes to `/v1/apps/<appSlug>/data-sources/<dataSourceSlug>/views`:
89`--appId | --appSlug`, `--dataSourceId | --dataSourceSlug`. Mutations also accept `--data '<json>'` or `--from-file ./view.json` (JSON only); explicit per-blob flags are **merged over** the file/data payload. Append `--json` for machine output.
90
91```bash
92docyrus studio list-data-views --appSlug crm --dataSourceSlug contacts --json
93docyrus studio get-data-view --appSlug crm --dataSourceSlug contacts --viewId <id> --json
94docyrus studio create-data-view --appSlug crm --dataSourceSlug contacts --from-file ./view.json --json
95docyrus studio update-data-view --appSlug crm --dataSourceSlug contacts --viewId <id> --from-file ./view.json --json
96docyrus studio delete-data-view --appSlug crm --dataSourceSlug contacts --viewId <id> --json
97```
98
99- `list-data-views` / `create-data-view` accept an optional `--tenantAppId` (maps to `tenant_app_id`) to scope a view to a different app than the data source's owner. Most views omit it.
100- `update-data-view` additionally accepts `--archived` (soft-delete / hide from the tab strip). `archived` is **not** the same as deleting — prefer `--archived true` over `delete-data-view` when you just want to retire a view.
101- `create-data-view` requires `name`. Everything else is optional; an omitted blob means "grid defaults" for that aspect.
102
103### Create a data view
104
105Minimal (just a named view — inherits all grid defaults):
106```bash
107docyrus studio create-data-view --appSlug crm --dataSourceSlug contacts \
108 --name "All Contacts" --isDefault true --sortOrder 1 --json
109```
110
111Configured (recommended via file — see [references/workflow-examples.md](references/workflow-examples.md) for full examples):
112```bash
113docyrus studio create-data-view --appSlug crm --dataSourceSlug contacts \
114 --from-file ./active-leads-view.json --json
115```
116
117## Critical rules
118
119- **Stored sub-keys ≠ UI property names.** Visible columns → `columns.visibility`, order → `columns.order`, pinning → `columns.pinning`, grouping → `columns.grouping`, color rules → `color_rules.row` / `color_rules.cell`, sort → `sort.sorting`, filters → `filters.columnFilters` / `filters.filterQuery`. Wrong key = silently ignored (view renders as defaults). Full list in [references/view-config-fields.md](references/view-config-fields.md).
120- **Blobs are not validated server-side.** The DTO only asserts each of `columns`/`filters`/`sort`/`color_rules` is an object. Malformed inner JSON is accepted and then ignored by the UI — so **always validate by reading back and checking the unpacked shape**; the create call succeeding proves nothing about correctness.
121- **Column ids are field slugs** (plus built-ins like `id`, `name`, `autonumber_id`). Get them from `list-fields`. A typo'd column id in `visibility`/`order`/`sorting` is dropped silently.
122- **Enum/relation filter values are UUIDs**, never labels (same rule as records). Read them from `list-enums` / the related data source before writing a `filters` blob that targets a select/status/relation field.
123- **`columns.visibility` is an allow/deny map, not a column list.** It is `{ "<colId>": true|false }`; columns absent from the map use their default visibility. To *hide* a column set it `false`; to force-show set `true`. Use `columns.order` to control sequence.
124- **Many features live inside the `columns` blob**, not their own column: paging (`pagingEnabled`/`pagingMode`/`pageSize`), inline editing (`inlineEditingEnabled`/`readOnlyColumns`), per-column options (`columnOptions`), and the bound form (`formId`). This is so the backend needs no schema change — see [references/view-config-fields.md](references/view-config-fields.md#the-columns-blob).
125- **`is_default` is per data source.** Marking a view default does not auto-unset other views server-side; if exactly-one-default matters, clear the previous default's `is_default` yourself. The UI fallback chain is: persisted localStorage selection → `is_default` view → first view.
126- **`quick_filter_fields` is a top-level `uuid[]` column** (field ids), not part of any blob.
127- **Validate then test, every time.** Read back the view and confirm the unpacked shape; delete throwaway test views you create.
128
129## Validate
130
131After authoring, confirm the saved view is exactly what you intended:
132
1331. `docyrus studio get-data-view --appSlug <app> --dataSourceSlug <ds> --viewId <id> --json` — confirm scalars (`name`, `is_default`, `sort_order`, `icon`, `color`, `archived`) and that each blob holds the **expected sub-keys** with valid column ids / enum UUIDs.
1342. Cross-check every column id in `columns.visibility`/`order`/`grouping`, every `sort.sorting[].id`, every filter field, and every `color_rules` formula reference against `list-fields` output — drop anything that does not resolve.
1353. `docyrus studio list-data-views ... --json` — confirm the view appears, ordering (`sort_order`), and that at most one `is_default` is set if that was the intent.
136
137Per-field "what correct looks like" checklist is in [references/workflow-examples.md](references/workflow-examples.md#validation-checklist).
138
139## Test
140
141Prove the view behaves as intended:
142
1431. **Round-trip check** — re-read the view and unpack each blob using the key map in [references/view-config-fields.md](references/view-config-fields.md); confirm it yields the visible columns / order / sort / filter you designed.
1442. **Filter sanity** — run the equivalent query against real records to confirm the filter selects the right rows, e.g. `docyrus ds list <app> <ds> --columns "..." --filter "..."` (see docyrus-cli-app), or `docyrus dsql` for complex filters. The view's `filters.filterQuery` mirrors react-querybuilder; the column filters mirror TanStack `columnFilters`.
1453. **Clean up** — `docyrus studio delete-data-view ... --viewId <id>` for throwaway views (or `--archived true` to retire without deleting).
146
147Full test playbook is in [references/workflow-examples.md](references/workflow-examples.md#test-playbook).
148
149## References
150
151- **[references/view-config-fields.md](references/view-config-fields.md)** — The authoritative field reference: every scalar column, the exact packed sub-keys of `columns` / `filters` / `sort` / `color_rules`, their types, accepted values (display modes, row heights, paging modes), the UI-name↔stored-key mapping, and gotchas. **Read this before writing any non-trivial view payload.**
152- **[references/workflow-examples.md](references/workflow-examples.md)** — End-to-end worked examples (default view, filtered+sorted+grouped view, color-coded view, gallery view, paging + inline-editing view, form-bound view) as copy-adaptable JSON payloads, plus the validation checklist and test playbook.