Dashboard schema — UI Protocol reference
Dashboard schemas of the ObjectStack UI Protocol: Dashboard, DashboardHeader and 8 more — each property with its type, default and a TypeScript example.
Source: packages/spec/src/ui/dashboard.zod.ts
TypeScript Usage
import { DashboardSchema, DashboardHeaderSchema, DashboardHeaderActionSchema, DashboardWidgetSchema, DashboardWidgetChartConfigSchema, DashboardWidgetOptionsSchema, GlobalFilterSchema, GlobalFilterOptionsFromSchema, WidgetActionTypeSchema, WidgetColorVariantSchema } from '@objectstack/spec/ui';
import type { Dashboard, DashboardHeader, DashboardHeaderAction, DashboardWidget, DashboardWidgetChartConfig, DashboardWidgetOptions, GlobalFilter, GlobalFilterOptionsFrom, WidgetActionType, WidgetColorVariant } from '@objectstack/spec/ui';
// Validate data
const result = DashboardSchema.parse(data);Dashboard
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Dashboard unique name |
| label | string | Record<string, string> | ✅ | Dashboard label |
| description | string | Record<string, string> | optional | Dashboard description |
| header | { showTitle?: boolean; showDescription?: boolean; actions?: object[] } | optional | Dashboard header configuration |
| widgets | { id: string; title?: string | Record<string, string>; description?: string | Record<string, string>; type?: Enum<'bar' | 'horizontal-bar' | 'column' | 'line' | 'area' | 'pie' | 'donut' | …>; … }[] | ✅ | Widgets to display |
| columns | integer | optional | Number of grid columns (default 12) |
| gap | integer | optional | Space between widgets, in steps of 0.25rem (4 = 1rem) |
| refreshIntervalSeconds | number | optional | Auto-refresh interval in seconds |
| refreshInterval | never | optional | [REMOVED] dashboard.refreshInterval was renamed to refreshIntervalSeconds in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to refreshIntervalSeconds; the value (seconds) is unchanged. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| dateRange | { field?: string; defaultRange?: Enum<'today' | 'yesterday' | 'this_week' | 'last_week' | 'this_month' | 'last_month' | …>; allowCustomRange?: boolean } | optional | Global dashboard date range filter configuration |
| globalFilters | { name?: string; field: string; object?: string; label?: string | Record<string, string>; … }[] | optional | Global filters that apply to all widgets in the dashboard |
| requiredPermissions | string[] | optional | [ADR-0066] Capabilities a user must ALL hold for this list view or dashboard to be served to them — names that permission sets grant through systemPermissions, the same list as requiredPermissions on an app, a navigation item or an action. Absent or empty: no audience gate. There is no any-of form: to serve one item to several audiences (legal OR admin), declare one capability and grant it to each of their permission sets. The server enforces it: a user who does not hold them all is not listed the item and is refused it by name; a view container's list views are pruned the same way, and so are an object's own list views for a user who may not edit the object. |
| aria | never | optional | [REMOVED] dashboard.aria was removed in @objectstack/spec 17.0.0 (audit close-out) — no dashboard renderer ever applied it, so declared ARIA attributes silently did not reach the DOM. Delete the key. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| performance | never | optional | [REMOVED] dashboard.performance was removed in @objectstack/spec 17.0.0 (audit close-out) — no renderer or runtime read it; dashboard performance tuning was never implemented. Delete the key. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| protection | { lock: Enum<'none' | 'no-overlay' | 'no-delete' | 'full'>; reason: string; docsUrl?: string } | optional | Package author protection block — lock policy for this dashboard. |
| _lock | Enum<'none' | 'no-overlay' | 'no-delete' | 'full'> | optional | Item-level lock — controls overlay & delete (ADR-0010). |
| _lockReason | string | optional | Human-readable reason shown when a write is refused by _lock. |
| _lockSource | Enum<'artifact' | 'package' | 'env-forced'> | optional | Layer that set _lock (artifact | package | env-forced). |
| _provenance | Enum<'package' | 'org' | 'env-forced'> | optional | Origin of the item (package | org | env-forced). |
| _packageId | string | optional | Owning package machine id. |
| _packageVersion | string | optional | Owning package version. |
| _lockDocsUrl | string | optional | Optional documentation link surfaced next to _lockReason. |
Nested Shape: Dashboard.header
| Property | Type | Required | Description |
|---|---|---|---|
| showTitle | boolean | optional (default: true) | Show dashboard title in header |
| showDescription | boolean | optional (default: true) | Show dashboard description in header |
| actions | { label: string | Record<string, string>; actionUrl: string; actionType?: Enum<'script' | 'url' | 'modal' | 'flow' | 'api' | 'form'>; icon?: string }[] | optional | Header action buttons |
Nested Shape: Dashboard.widgets[number]
| Property | Type | Required | Description |
|---|---|---|---|
| id | string | ✅ | Unique widget identifier (snake_case) |
| title | string | Record<string, string> | optional | Widget title |
| description | string | Record<string, string> | optional | Widget description text below the header |
| type | Enum<'bar' | 'horizontal-bar' | 'column' | 'line' | 'area' | 'pie' | 'donut' | …> | optional (default: "metric") | Visualization type |
| chartConfig | { title?: string | Record<string, string>; subtitle?: string | Record<string, string>; description?: string | Record<string, string>; colors?: string[] | Record<string, string>; … } | optional | Chart appearance: title, subtitle, description, colors, height, legend, data labels, annotations, interaction. The bound dataset decides which series exist and which column each one reads (ADR-0021), so type (the widget's own type wins), xAxis, yAxis and series are refused here — select dimensions / values instead. |
| colorVariant | Enum<'default' | 'blue' | 'teal' | 'orange' | 'purple' | 'success' | 'warning' | 'danger'> | optional | Widget color variant for theming |
| requiresObject | string | optional | Hide the widget unless the named object is registered |
| requiresService | string | optional | Hide the widget unless the named kernel service is registered |
| actionUrl | never | optional | [REMOVED] dashboard.widgets[].actionUrl was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from header.actions[]. The three keys actionUrl / actionType / actionIcon went together; delete all three. Put the affordance on the dashboard header instead — header: { actions: [{ label, actionUrl, actionType, icon }] } — which IS dispatched (DashboardHeaderAction, same vocabulary, and icon is the header spelling of actionIcon). For a per-ROW affordance, the widget to reach for is a table/pivot bound to a dataset: its rows are clickable and drill through the semantic layer. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| actionType | never | optional | [REMOVED] dashboard.widgets[].actionType was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from header.actions[]. The three keys actionUrl / actionType / actionIcon went together; delete all three. Put the affordance on the dashboard header instead — header: { actions: [{ label, actionUrl, actionType, icon }] } — which IS dispatched (DashboardHeaderAction, same vocabulary, and icon is the header spelling of actionIcon). For a per-ROW affordance, the widget to reach for is a table/pivot bound to a dataset: its rows are clickable and drill through the semantic layer. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| actionIcon | never | optional | [REMOVED] dashboard.widgets[].actionIcon was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from header.actions[]. The three keys actionUrl / actionType / actionIcon went together; delete all three. Put the affordance on the dashboard header instead — header: { actions: [{ label, actionUrl, actionType, icon }] } — which IS dispatched (DashboardHeaderAction, same vocabulary, and icon is the header spelling of actionIcon). For a per-ROW affordance, the widget to reach for is a table/pivot bound to a dataset: its rows are clickable and drill through the semantic layer. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| filter | any | optional | Presentation-scope filter (runtimeFilter) |
| compareTo | { kind: Enum<'previousPeriod' | 'previousYear'>; dimension?: string } | optional | Period-over-period comparison window ({ kind, dimension? }) |
| dataset | string | ✅ | Dataset name to bind (ADR-0021) |
| dimensions | string[] | optional | Dimension names — X/group/split |
| values | string[] | ✅ | Measure names — Y (at least one; exactly one on the metric/kpi/gauge/solid-gauge/bullet family and on the single-series pie/donut/funnel/treemap/sankey; with no dimensions, two or more only on table/pivot/bar/column/horizontal-bar/line/area/combo) |
| layout | { x: number; y: number; w: number; h: number } | optional | Grid layout position (auto-flowed when omitted) |
| options | { dateGranularity?: Enum<'day' | 'week' | 'month' | 'quarter' | 'year'>; sortBy?: string; sortOrder?: Enum<'asc' | 'desc'>; limit?: integer; … } & Record<string, any> | optional | Widget specific configuration |
| filterBindings | Record<string, string | false> | optional | Per-widget dashboard-filter bindings: filter name → this widget's field, or false to opt out |
| suppressWarnings | string[] | optional | Build diagnostic rule ids suppressed on this widget |
| responsive | never | optional | [REMOVED] dashboard.widgets[].responsive was removed in @objectstack/spec 17.0.0 (ADR-0049 D2) — no renderer ever read it, so per-widget breakpoint overrides were never applied: the value parsed, validated, and then did nothing. The dashboard grid reflows by its own layout rules (columns + gap on the dashboard, the layout box on each widget). Delete the key. This message used to point at page.components[].responsive as the live home of the shared ResponsiveConfig shape; that key was measured equally unread and removed with the shape with it. For breakpoint behaviour that IS applied, use responsiveStyles on a page component (ADR-0065) — per-breakpoint CSS maps compiled to id-scoped CSS at render. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| aria | never | optional | [REMOVED] dashboard.widgets[].aria was removed in @objectstack/spec 17.0.0 (ADR-0049 D2) — no renderer ever applied it, so ARIA attributes declared on a widget silently did not reach the DOM: the key promised accessibility compliance it did not deliver. This is the same removal the dashboard-level aria got in 17.0.0. Delete the key. The dashboard renderer emits its own aria-* attributes for the widget grid; author a title (and description) on the widget instead — those ARE what the renderer labels the card with. The shared AriaProps shape is NOT gone: it stays live on page.aria, page.components[].aria and the list view aria. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
Nested Shape: Dashboard.dateRange
| Property | Type | Required | Description |
|---|---|---|---|
| field | string | optional | Default date field name for time-based filtering |
| defaultRange | Enum<'today' | 'yesterday' | 'this_week' | 'last_week' | 'this_month' | 'last_month' | …> | optional (default: "this_month") | Default date range preset |
| allowCustomRange | boolean | optional (default: true) | Allow users to pick a custom date range |
Nested Shape: Dashboard.globalFilters[number]
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | optional | Stable filter name (variable key); defaults to field |
| field | string | ✅ | Field name to filter on — at the authoring layer it resolves against the object behind each bound widget's dataset (dataset.object), not against that dataset's declared dimensions; enforced by the lint rule dashboard-filter-field-unknown (severity error) |
| object | string | optional | Object whose fields.<object>.<field> translation-bundle entry resolves this filter's field label and option labels |
| label | string | Record<string, string> | optional | Display label for the filter |
| type | Enum<'text' | 'select' | 'date' | 'number' | 'lookup'> | optional | Filter input type |
| options | { value: string | number | boolean; label: string | Record<string, string> }[] | optional | Static filter options |
| optionsFrom | { object: string; valueField: string; labelField: string; filter?: any } | optional | Dynamic filter options from object |
| defaultValue | string | number | boolean | optional | Default filter value |
| scope | Enum<'dashboard' | 'widget'> | optional (default: "dashboard") | Filter application scope |
| targetWidgets | string[] | optional | Widget IDs to apply this filter to |
Nested Shape: Dashboard.protection
| Property | Type | Required | Description |
|---|---|---|---|
| lock | Enum<'none' | 'no-overlay' | 'no-delete' | 'full'> | ✅ | Lock policy — none | no-overlay | no-delete | full. |
| reason | string | ✅ | User-visible reason shown when the lock blocks an action. |
| docsUrl | string | optional | Optional URL the Studio banner links to for more context. |
DashboardHeader
Dashboard header configuration
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| showTitle | boolean | optional (default: true) | Show dashboard title in header |
| showDescription | boolean | optional (default: true) | Show dashboard description in header |
| actions | { label: string | Record<string, string>; actionUrl: string; actionType?: Enum<'script' | 'url' | 'modal' | 'flow' | 'api' | 'form'>; icon?: string }[] | optional | Header action buttons |
Nested Shape: DashboardHeader.actions[number]
Dashboard header action
| Property | Type | Required | Description |
|---|---|---|---|
| label | string | Record<string, string> | ✅ | Action button label |
| actionUrl | string | ✅ | URL or target for the action. When actionType is 'url' or omitted: an http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ — every other scheme and a protocol-relative //host are refused. |
| actionType | Enum<'script' | 'url' | 'modal' | 'flow' | 'api' | 'form'> | optional | Type of action |
| icon | string | optional | Icon identifier for the action button |
DashboardHeaderAction
Dashboard header action
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| label | string | Record<string, string> | ✅ | Action button label |
| actionUrl | string | ✅ | URL or target for the action. When actionType is 'url' or omitted: an http:// or https:// URL, a mailto: address, or an in-app path starting with / or ./ — every other scheme and a protocol-relative //host are refused. |
| actionType | Enum<'script' | 'url' | 'modal' | 'flow' | 'api' | 'form'> | optional | Type of action |
| icon | string | optional | Icon identifier for the action button |
DashboardWidget
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| id | string | ✅ | Unique widget identifier (snake_case) |
| title | string | Record<string, string> | optional | Widget title |
| description | string | Record<string, string> | optional | Widget description text below the header |
| type | Enum<'bar' | 'horizontal-bar' | 'column' | 'line' | 'area' | 'pie' | 'donut' | 'funnel' | 'scatter' | 'treemap' | 'sankey' | 'combo' | 'gauge' | 'solid-gauge' | … +6 more> | optional (default: "metric") | Visualization type |
| chartConfig | { title?: string | Record<string, string>; subtitle?: string | Record<string, string>; description?: string | Record<string, string>; colors?: string[] | Record<string, string>; … } | optional | Chart appearance: title, subtitle, description, colors, height, legend, data labels, annotations, interaction. The bound dataset decides which series exist and which column each one reads (ADR-0021), so type (the widget's own type wins), xAxis, yAxis and series are refused here — select dimensions / values instead. |
| colorVariant | Enum<'default' | 'blue' | 'teal' | 'orange' | 'purple' | 'success' | 'warning' | 'danger'> | optional | Widget color variant for theming |
| requiresObject | string | optional | Hide the widget unless the named object is registered |
| requiresService | string | optional | Hide the widget unless the named kernel service is registered |
| actionUrl | never | optional | [REMOVED] dashboard.widgets[].actionUrl was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from header.actions[]. The three keys actionUrl / actionType / actionIcon went together; delete all three. Put the affordance on the dashboard header instead — header: { actions: [{ label, actionUrl, actionType, icon }] } — which IS dispatched (DashboardHeaderAction, same vocabulary, and icon is the header spelling of actionIcon). For a per-ROW affordance, the widget to reach for is a table/pivot bound to a dataset: its rows are clickable and drill through the semantic layer. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| actionType | never | optional | [REMOVED] dashboard.widgets[].actionType was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from header.actions[]. The three keys actionUrl / actionType / actionIcon went together; delete all three. Put the affordance on the dashboard header instead — header: { actions: [{ label, actionUrl, actionType, icon }] } — which IS dispatched (DashboardHeaderAction, same vocabulary, and icon is the header spelling of actionIcon). For a per-ROW affordance, the widget to reach for is a table/pivot bound to a dataset: its rows are clickable and drill through the semantic layer. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| actionIcon | never | optional | [REMOVED] dashboard.widgets[].actionIcon was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from header.actions[]. The three keys actionUrl / actionType / actionIcon went together; delete all three. Put the affordance on the dashboard header instead — header: { actions: [{ label, actionUrl, actionType, icon }] } — which IS dispatched (DashboardHeaderAction, same vocabulary, and icon is the header spelling of actionIcon). For a per-ROW affordance, the widget to reach for is a table/pivot bound to a dataset: its rows are clickable and drill through the semantic layer. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| filter | any | optional | Presentation-scope filter (runtimeFilter) |
| compareTo | { kind: Enum<'previousPeriod' | 'previousYear'>; dimension?: string } | optional | Period-over-period comparison window ({ kind, dimension? }) |
| dataset | string | ✅ | Dataset name to bind (ADR-0021) |
| dimensions | string[] | optional | Dimension names — X/group/split |
| values | string[] | ✅ | Measure names — Y (at least one; exactly one on the metric/kpi/gauge/solid-gauge/bullet family and on the single-series pie/donut/funnel/treemap/sankey; with no dimensions, two or more only on table/pivot/bar/column/horizontal-bar/line/area/combo) |
| layout | { x: number; y: number; w: number; h: number } | optional | Grid layout position (auto-flowed when omitted) |
| options | { dateGranularity?: Enum<'day' | 'week' | 'month' | 'quarter' | 'year'>; sortBy?: string; sortOrder?: Enum<'asc' | 'desc'>; limit?: integer; … } & Record<string, any> | optional | Widget specific configuration |
| filterBindings | Record<string, string | false> | optional | Per-widget dashboard-filter bindings: filter name → this widget's field, or false to opt out |
| suppressWarnings | string[] | optional | Build diagnostic rule ids suppressed on this widget |
| responsive | never | optional | [REMOVED] dashboard.widgets[].responsive was removed in @objectstack/spec 17.0.0 (ADR-0049 D2) — no renderer ever read it, so per-widget breakpoint overrides were never applied: the value parsed, validated, and then did nothing. The dashboard grid reflows by its own layout rules (columns + gap on the dashboard, the layout box on each widget). Delete the key. This message used to point at page.components[].responsive as the live home of the shared ResponsiveConfig shape; that key was measured equally unread and removed with the shape with it. For breakpoint behaviour that IS applied, use responsiveStyles on a page component (ADR-0065) — per-breakpoint CSS maps compiled to id-scoped CSS at render. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| aria | never | optional | [REMOVED] dashboard.widgets[].aria was removed in @objectstack/spec 17.0.0 (ADR-0049 D2) — no renderer ever applied it, so ARIA attributes declared on a widget silently did not reach the DOM: the key promised accessibility compliance it did not deliver. This is the same removal the dashboard-level aria got in 17.0.0. Delete the key. The dashboard renderer emits its own aria-* attributes for the widget grid; author a title (and description) on the widget instead — those ARE what the renderer labels the card with. The shared AriaProps shape is NOT gone: it stays live on page.aria, page.components[].aria and the list view aria. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
Allowed Values: DashboardWidget.type
barhorizontal-barcolumnlineareapiedonutfunnelscattertreemapsankeycombogaugesolid-gaugemetrickpibulletradartablepivot
Nested Shape: DashboardWidget.chartConfig
| Property | Type | Required | Description |
|---|---|---|---|
| type | never | optional | [REMOVED] dashboard.widgets[].chartConfig.type was removed in @objectstack/spec 17.5.0 (ADR-0021 · ADR-0049 D2) — on a dataset-bound widget the dataset already decides nothing about the chart family — the WIDGET's own type does, and it always won: the dashboard renderer maps the widget type to the chart family and never reads this key, so the authored value could only agree with the dataset selection or silently disagree with it. Delete the key. Write the family on the widget instead: type: 'line' beside dataset, not inside chartConfig. The key is NOT gone from the chart config itself: it stays authorable on the react <ObjectChart> tier, where the chart is bound to inline data and there is no dataset to derive it from. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| title | string | Record<string, string> | optional | Chart title |
| subtitle | string | Record<string, string> | optional | Chart subtitle |
| description | string | Record<string, string> | optional | Accessibility description — announced to screen readers as the chart’s label |
| xAxis | never | optional | [REMOVED] dashboard.widgets[].chartConfig.xAxis was removed in @objectstack/spec 17.5.0 (ADR-0021 · ADR-0049 D2) — on a dataset-bound widget the dataset already decides which column the category axis plots — it is the dataset DIMENSION the widget selects, so the authored value could only agree with the dataset selection or silently disagree with it. Delete the key. Select the dimension instead: dimensions: ['stage'] on the widget. Axis APPEARANCE (title, number format, min/max, grid lines, log scale) has no home on a dataset-bound widget — the dataset's dimension declaration is what labels and formats the axis. The key is NOT gone from the chart config itself: it stays authorable on the react <ObjectChart> tier, where the chart is bound to inline data and there is no dataset to derive it from. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| yAxis | never | optional | [REMOVED] dashboard.widgets[].chartConfig.yAxis was removed in @objectstack/spec 17.5.0 (ADR-0021 · ADR-0049 D2) — on a dataset-bound widget the dataset already decides which columns the value axis plots — they are the dataset MEASURES the widget selects, so the authored value could only agree with the dataset selection or silently disagree with it. Delete the key. Select the measures instead: values: ['amount'] on the widget, one entry per mark. A second axis is a second measure, not a second axis declaration. The key is NOT gone from the chart config itself: it stays authorable on the react <ObjectChart> tier, where the chart is bound to inline data and there is no dataset to derive it from. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| series | never | optional | [REMOVED] dashboard.widgets[].chartConfig.series was removed in @objectstack/spec 17.5.0 (ADR-0021 · ADR-0049 D2) — on a dataset-bound widget the dataset already decides which series exist and which column each one reads — series membership follows from the measures in values and the split in dimensions, so the authored value could only agree with the dataset selection or silently disagree with it. Delete the key. Select the measures and the split instead: values declares one series per measure and a second dimensions entry splits them. Per-series appearance (label, colour, mark type, stacking) is not authorable on a dataset-bound widget; colors on this same chart config is the palette channel that remains. The key is NOT gone from the chart config itself: it stays authorable on the react <ObjectChart> tier, where the chart is bound to inline data and there is no dataset to derive it from. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| colors | string[] | Record<string, string> | optional | Color palette (string[]) or value→color map ({ value: color }) |
| height | number | optional | Fixed plot height in pixels (overrides the container default) |
| showLegend | boolean | optional (default: true) | Display legend |
| showDataLabels | boolean | optional (default: false) | Display data labels |
| annotations | { type?: Enum<'line' | 'region'>; axis?: Enum<'x' | 'y'>; value: number | string; endValue?: number | string; … }[] | optional | Reference lines/bands drawn over the plot: { type: "line" | "region", axis: "x" | "y", value, endValue?, color?, label?, style? } |
| interaction | { tooltips?: boolean; brush?: boolean } | optional | Interaction toggles: { tooltips?, brush? } |
| aria | never | optional | [REMOVED] ChartConfig.aria — authored as dashboard.widgets[].chartConfig.aria, report.chart.aria and report.blocks[].chart.aria — was removed in @objectstack/spec 17 (ADR-0049 D2). No chart renderer ever applied it: the chart implementation declares no aria prop, the presentation lowering names it nowhere, and the react <ObjectChart> block never published it, so ARIA attributes declared here parsed and then silently did not reach the DOM. Delete the key. The accessible name that IS applied on this same chart config is its sibling description, which the chart renderer lowers onto the chart graphic as role="img" plus aria-label. The shared AriaProps shape is NOT gone — ariaLabel / ariaDescribedBy / role stay live in the aria block on page.aria, page.components[].aria and the list view aria. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
Nested Shape: DashboardWidget.compareTo
| Property | Type | Required | Description |
|---|---|---|---|
| kind | Enum<'previousPeriod' | 'previousYear'> | ✅ | Comparison window: previousPeriod (equal-length, immediately before) or previousYear (−1 calendar year) |
| dimension | string | optional | Time dimension to shift; omit when the selection has exactly one dated time dimension |
Nested Shape: DashboardWidget.options
| Property | Type | Required | Description |
|---|---|---|---|
| dateGranularity | Enum<'day' | 'week' | 'month' | 'quarter' | 'year'> | optional | Bucket selected date dimensions (day/week/month/quarter/year) |
| sortBy | string | optional | Dimension/measure name to order by |
| sortOrder | Enum<'asc' | 'desc'> | optional | Sort direction for sortBy |
| limit | integer | optional | Max rows (applied after ordering) |
| stageOrder | (string | number | boolean)[] | optional | Explicit stage order for a funnel widget, as the dimension's stored values. funnel is the only widget type that reads it, and the schema refuses it on any other type rather than accepting an order nothing consults — order those with sortBy/sortOrder instead. There is no pyramid widget type — write funnel. |
DashboardWidgetChartConfig
Chart APPEARANCE for a dataset-bound widget (ADR-0021): title, subtitle, description, colors, height, legend, data labels, annotations and interaction. The dataset decides which series exist and which column each one reads, so type, xAxis, yAxis and series are refused here — write them as the widget's own type and its dimensions / values selection.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| type | never | optional | [REMOVED] dashboard.widgets[].chartConfig.type was removed in @objectstack/spec 17.5.0 (ADR-0021 · ADR-0049 D2) — on a dataset-bound widget the dataset already decides nothing about the chart family — the WIDGET's own type does, and it always won: the dashboard renderer maps the widget type to the chart family and never reads this key, so the authored value could only agree with the dataset selection or silently disagree with it. Delete the key. Write the family on the widget instead: type: 'line' beside dataset, not inside chartConfig. The key is NOT gone from the chart config itself: it stays authorable on the react <ObjectChart> tier, where the chart is bound to inline data and there is no dataset to derive it from. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| title | string | Record<string, string> | optional | Chart title |
| subtitle | string | Record<string, string> | optional | Chart subtitle |
| description | string | Record<string, string> | optional | Accessibility description — announced to screen readers as the chart’s label |
| xAxis | never | optional | [REMOVED] dashboard.widgets[].chartConfig.xAxis was removed in @objectstack/spec 17.5.0 (ADR-0021 · ADR-0049 D2) — on a dataset-bound widget the dataset already decides which column the category axis plots — it is the dataset DIMENSION the widget selects, so the authored value could only agree with the dataset selection or silently disagree with it. Delete the key. Select the dimension instead: dimensions: ['stage'] on the widget. Axis APPEARANCE (title, number format, min/max, grid lines, log scale) has no home on a dataset-bound widget — the dataset's dimension declaration is what labels and formats the axis. The key is NOT gone from the chart config itself: it stays authorable on the react <ObjectChart> tier, where the chart is bound to inline data and there is no dataset to derive it from. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| yAxis | never | optional | [REMOVED] dashboard.widgets[].chartConfig.yAxis was removed in @objectstack/spec 17.5.0 (ADR-0021 · ADR-0049 D2) — on a dataset-bound widget the dataset already decides which columns the value axis plots — they are the dataset MEASURES the widget selects, so the authored value could only agree with the dataset selection or silently disagree with it. Delete the key. Select the measures instead: values: ['amount'] on the widget, one entry per mark. A second axis is a second measure, not a second axis declaration. The key is NOT gone from the chart config itself: it stays authorable on the react <ObjectChart> tier, where the chart is bound to inline data and there is no dataset to derive it from. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| series | never | optional | [REMOVED] dashboard.widgets[].chartConfig.series was removed in @objectstack/spec 17.5.0 (ADR-0021 · ADR-0049 D2) — on a dataset-bound widget the dataset already decides which series exist and which column each one reads — series membership follows from the measures in values and the split in dimensions, so the authored value could only agree with the dataset selection or silently disagree with it. Delete the key. Select the measures and the split instead: values declares one series per measure and a second dimensions entry splits them. Per-series appearance (label, colour, mark type, stacking) is not authorable on a dataset-bound widget; colors on this same chart config is the palette channel that remains. The key is NOT gone from the chart config itself: it stays authorable on the react <ObjectChart> tier, where the chart is bound to inline data and there is no dataset to derive it from. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
| colors | string[] | Record<string, string> | optional | Color palette (string[]) or value→color map ({ value: color }) |
| height | number | optional | Fixed plot height in pixels (overrides the container default) |
| showLegend | boolean | optional (default: true) | Display legend |
| showDataLabels | boolean | optional (default: false) | Display data labels |
| annotations | { type?: Enum<'line' | 'region'>; axis?: Enum<'x' | 'y'>; value: number | string; endValue?: number | string; … }[] | optional | Reference lines/bands drawn over the plot: { type: "line" | "region", axis: "x" | "y", value, endValue?, color?, label?, style? } |
| interaction | { tooltips?: boolean; brush?: boolean } | optional | Interaction toggles: { tooltips?, brush? } |
| aria | never | optional | [REMOVED] ChartConfig.aria — authored as dashboard.widgets[].chartConfig.aria, report.chart.aria and report.blocks[].chart.aria — was removed in @objectstack/spec 17 (ADR-0049 D2). No chart renderer ever applied it: the chart implementation declares no aria prop, the presentation lowering names it nowhere, and the react <ObjectChart> block never published it, so ARIA attributes declared here parsed and then silently did not reach the DOM. Delete the key. The accessible name that IS applied on this same chart config is its sibling description, which the chart renderer lowers onto the chart graphic as role="img" plus aria-label. The shared AriaProps shape is NOT gone — ariaLabel / ariaDescribedBy / role stay live in the aria block on page.aria, page.components[].aria and the list view aria. Run os migrate meta --from 17 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand. |
Nested Shape: DashboardWidgetChartConfig.annotations[number]
| Property | Type | Required | Description |
|---|---|---|---|
| type | Enum<'line' | 'region'> | optional (default: "line") | |
| axis | Enum<'x' | 'y'> | optional (default: "y") | |
| value | number | string | ✅ | Start value |
| endValue | number | string | optional | End value for regions |
| color | string | optional | |
| label | string | Record<string, string> | optional | Display label — the default-language string, or an inline locale map ({ en, "zh-CN" }) resolved at render time |
| style | Enum<'solid' | 'dashed' | 'dotted'> | optional (default: "dashed") |
Nested Shape: DashboardWidgetChartConfig.interaction
| Property | Type | Required | Description |
|---|---|---|---|
| tooltips | boolean | optional (default: true) | Show the hover tooltip |
| brush | boolean | optional (default: false) | Show the range selector under the plot |
DashboardWidgetOptions
Widget configuration — declared query keys + open renderer extras
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| dateGranularity | Enum<'day' | 'week' | 'month' | 'quarter' | 'year'> | optional | Bucket selected date dimensions (day/week/month/quarter/year) |
| sortBy | string | optional | Dimension/measure name to order by |
| sortOrder | Enum<'asc' | 'desc'> | optional | Sort direction for sortBy |
| limit | integer | optional | Max rows (applied after ordering) |
| stageOrder | (string | number | boolean)[] | optional | Explicit stage order for a funnel widget, as the dimension's stored values. funnel is the only widget type that reads it, and the schema refuses it on any other type rather than accepting an order nothing consults — order those with sortBy/sortOrder instead. There is no pyramid widget type — write funnel. |
GlobalFilter
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | string | optional | Stable filter name (variable key); defaults to field |
| field | string | ✅ | Field name to filter on — at the authoring layer it resolves against the object behind each bound widget's dataset (dataset.object), not against that dataset's declared dimensions; enforced by the lint rule dashboard-filter-field-unknown (severity error) |
| object | string | optional | Object whose fields.<object>.<field> translation-bundle entry resolves this filter's field label and option labels |
| label | string | Record<string, string> | optional | Display label for the filter |
| type | Enum<'text' | 'select' | 'date' | 'number' | 'lookup'> | optional | Filter input type |
| options | { value: string | number | boolean; label: string | Record<string, string> }[] | optional | Static filter options |
| optionsFrom | { object: string; valueField: string; labelField: string; filter?: any } | optional | Dynamic filter options from object |
| defaultValue | string | number | boolean | optional | Default filter value |
| scope | Enum<'dashboard' | 'widget'> | optional (default: "dashboard") | Filter application scope |
| targetWidgets | string[] | optional | Widget IDs to apply this filter to |
Nested Shape: GlobalFilter.options[number]
| Property | Type | Required | Description |
|---|---|---|---|
| value | string | number | boolean | ✅ | Option value |
| label | string | Record<string, string> | ✅ | Display label — the default-language string, or an inline locale map ({ en, "zh-CN" }) resolved at render time |
Nested Shape: GlobalFilter.optionsFrom
| Property | Type | Required | Description |
|---|---|---|---|
| object | string | ✅ | Source object name |
| valueField | string | ✅ | Field to use as option value |
| labelField | string | ✅ | Field to use as option label |
| filter | any | optional | Filter to apply to source object |
GlobalFilterOptionsFrom
Dynamic filter options from object
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| object | string | ✅ | Source object name |
| valueField | string | ✅ | Field to use as option value |
| labelField | string | ✅ | Field to use as option label |
| filter | any | optional | Filter to apply to source object |
WidgetActionType
Widget action type
Allowed Values
scripturlmodalflowapiform
WidgetColorVariant
Widget color variant
Allowed Values
defaultbluetealorangepurplesuccesswarningdanger
Component
Component schemas of the ObjectStack UI Protocol: ActionButtonProps and 43 more — each property with its type, default and a TypeScript example.
Dataset
Analytics Dataset — the one semantic layer. Reference for Dataset, DatasetDimension, DatasetMeasure, DerivedMeasureOp: every property with its type and default.