ObjectStackObjectStack

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

PropertyTypeRequiredDescription
namestring✅Dashboard unique name
labelstring | Record<string, string>✅Dashboard label
descriptionstring | Record<string, string>optionalDashboard description
header{ showTitle?: boolean; showDescription?: boolean; actions?: object[] }optionalDashboard 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
columnsintegeroptionalNumber of grid columns (default 12)
gapintegeroptionalSpace between widgets, in steps of 0.25rem (4 = 1rem)
refreshIntervalSecondsnumberoptionalAuto-refresh interval in seconds
refreshIntervalneveroptional[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 }optionalGlobal dashboard date range filter configuration
globalFilters{ name?: string; field: string; object?: string; label?: string | Record<string, string>; … }[]optionalGlobal filters that apply to all widgets in the dashboard
requiredPermissionsstring[]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.
arianeveroptional[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.
performanceneveroptional[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 }optionalPackage author protection block — lock policy for this dashboard.
_lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>optionalItem-level lock — controls overlay & delete (ADR-0010).
_lockReasonstringoptionalHuman-readable reason shown when a write is refused by _lock.
_lockSourceEnum<'artifact' | 'package' | 'env-forced'>optionalLayer that set _lock (artifact | package | env-forced).
_provenanceEnum<'package' | 'org' | 'env-forced'>optionalOrigin of the item (package | org | env-forced).
_packageIdstringoptionalOwning package machine id.
_packageVersionstringoptionalOwning package version.
_lockDocsUrlstringoptionalOptional documentation link surfaced next to _lockReason.

Nested Shape: Dashboard.header

PropertyTypeRequiredDescription
showTitlebooleanoptional (default: true)Show dashboard title in header
showDescriptionbooleanoptional (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 }[]optionalHeader action buttons

Nested Shape: Dashboard.widgets[number]

PropertyTypeRequiredDescription
idstring✅Unique widget identifier (snake_case)
titlestring | Record<string, string>optionalWidget title
descriptionstring | Record<string, string>optionalWidget description text below the header
typeEnum<'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>; … }optionalChart 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.
colorVariantEnum<'default' | 'blue' | 'teal' | 'orange' | 'purple' | 'success' | 'warning' | 'danger'>optionalWidget color variant for theming
requiresObjectstringoptionalHide the widget unless the named object is registered
requiresServicestringoptionalHide the widget unless the named kernel service is registered
actionUrlneveroptional[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.
actionTypeneveroptional[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.
actionIconneveroptional[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.
filteranyoptionalPresentation-scope filter (runtimeFilter)
compareTo{ kind: Enum<'previousPeriod' | 'previousYear'>; dimension?: string }optionalPeriod-over-period comparison window ({ kind, dimension? })
datasetstring✅Dataset name to bind (ADR-0021)
dimensionsstring[]optionalDimension names — X/group/split
valuesstring[]✅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 }optionalGrid 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>optionalWidget specific configuration
filterBindingsRecord<string, string | false>optionalPer-widget dashboard-filter bindings: filter name → this widget's field, or false to opt out
suppressWarningsstring[]optionalBuild diagnostic rule ids suppressed on this widget
responsiveneveroptional[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.
arianeveroptional[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

PropertyTypeRequiredDescription
fieldstringoptionalDefault date field name for time-based filtering
defaultRangeEnum<'today' | 'yesterday' | 'this_week' | 'last_week' | 'this_month' | 'last_month' | …>optional (default: "this_month")Default date range preset
allowCustomRangebooleanoptional (default: true)Allow users to pick a custom date range

Nested Shape: Dashboard.globalFilters[number]

PropertyTypeRequiredDescription
namestringoptionalStable filter name (variable key); defaults to field
fieldstring✅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)
objectstringoptionalObject whose fields.<object>.<field> translation-bundle entry resolves this filter's field label and option labels
labelstring | Record<string, string>optionalDisplay label for the filter
typeEnum<'text' | 'select' | 'date' | 'number' | 'lookup'>optionalFilter input type
options{ value: string | number | boolean; label: string | Record<string, string> }[]optionalStatic filter options
optionsFrom{ object: string; valueField: string; labelField: string; filter?: any }optionalDynamic filter options from object
defaultValuestring | number | booleanoptionalDefault filter value
scopeEnum<'dashboard' | 'widget'>optional (default: "dashboard")Filter application scope
targetWidgetsstring[]optionalWidget IDs to apply this filter to

Nested Shape: Dashboard.protection

PropertyTypeRequiredDescription
lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>✅Lock policy — none | no-overlay | no-delete | full.
reasonstring✅User-visible reason shown when the lock blocks an action.
docsUrlstringoptionalOptional URL the Studio banner links to for more context.

DashboardHeader

Dashboard header configuration

Properties

PropertyTypeRequiredDescription
showTitlebooleanoptional (default: true)Show dashboard title in header
showDescriptionbooleanoptional (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 }[]optionalHeader action buttons

Nested Shape: DashboardHeader.actions[number]

Dashboard header action

PropertyTypeRequiredDescription
labelstring | Record<string, string>✅Action button label
actionUrlstring✅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.
actionTypeEnum<'script' | 'url' | 'modal' | 'flow' | 'api' | 'form'>optionalType of action
iconstringoptionalIcon identifier for the action button

DashboardHeaderAction

Dashboard header action

Properties

PropertyTypeRequiredDescription
labelstring | Record<string, string>✅Action button label
actionUrlstring✅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.
actionTypeEnum<'script' | 'url' | 'modal' | 'flow' | 'api' | 'form'>optionalType of action
iconstringoptionalIcon identifier for the action button

DashboardWidget

Properties

PropertyTypeRequiredDescription
idstring✅Unique widget identifier (snake_case)
titlestring | Record<string, string>optionalWidget title
descriptionstring | Record<string, string>optionalWidget description text below the header
typeEnum<'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>; … }optionalChart 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.
colorVariantEnum<'default' | 'blue' | 'teal' | 'orange' | 'purple' | 'success' | 'warning' | 'danger'>optionalWidget color variant for theming
requiresObjectstringoptionalHide the widget unless the named object is registered
requiresServicestringoptionalHide the widget unless the named kernel service is registered
actionUrlneveroptional[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.
actionTypeneveroptional[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.
actionIconneveroptional[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.
filteranyoptionalPresentation-scope filter (runtimeFilter)
compareTo{ kind: Enum<'previousPeriod' | 'previousYear'>; dimension?: string }optionalPeriod-over-period comparison window ({ kind, dimension? })
datasetstring✅Dataset name to bind (ADR-0021)
dimensionsstring[]optionalDimension names — X/group/split
valuesstring[]✅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 }optionalGrid 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>optionalWidget specific configuration
filterBindingsRecord<string, string | false>optionalPer-widget dashboard-filter bindings: filter name → this widget's field, or false to opt out
suppressWarningsstring[]optionalBuild diagnostic rule ids suppressed on this widget
responsiveneveroptional[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.
arianeveroptional[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

  • bar
  • horizontal-bar
  • column
  • line
  • area
  • pie
  • donut
  • funnel
  • scatter
  • treemap
  • sankey
  • combo
  • gauge
  • solid-gauge
  • metric
  • kpi
  • bullet
  • radar
  • table
  • pivot

Nested Shape: DashboardWidget.chartConfig

PropertyTypeRequiredDescription
typeneveroptional[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.
titlestring | Record<string, string>optionalChart title
subtitlestring | Record<string, string>optionalChart subtitle
descriptionstring | Record<string, string>optionalAccessibility description — announced to screen readers as the chart’s label
xAxisneveroptional[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.
yAxisneveroptional[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.
seriesneveroptional[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.
colorsstring[] | Record<string, string>optionalColor palette (string[]) or value→color map ({ value: color })
heightnumberoptionalFixed plot height in pixels (overrides the container default)
showLegendbooleanoptional (default: true)Display legend
showDataLabelsbooleanoptional (default: false)Display data labels
annotations{ type?: Enum<'line' | 'region'>; axis?: Enum<'x' | 'y'>; value: number | string; endValue?: number | string; … }[]optionalReference lines/bands drawn over the plot: { type: "line" | "region", axis: "x" | "y", value, endValue?, color?, label?, style? }
interaction{ tooltips?: boolean; brush?: boolean }optionalInteraction toggles: { tooltips?, brush? }
arianeveroptional[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

PropertyTypeRequiredDescription
kindEnum<'previousPeriod' | 'previousYear'>✅Comparison window: previousPeriod (equal-length, immediately before) or previousYear (−1 calendar year)
dimensionstringoptionalTime dimension to shift; omit when the selection has exactly one dated time dimension

Nested Shape: DashboardWidget.options

PropertyTypeRequiredDescription
dateGranularityEnum<'day' | 'week' | 'month' | 'quarter' | 'year'>optionalBucket selected date dimensions (day/week/month/quarter/year)
sortBystringoptionalDimension/measure name to order by
sortOrderEnum<'asc' | 'desc'>optionalSort direction for sortBy
limitintegeroptionalMax rows (applied after ordering)
stageOrder(string | number | boolean)[]optionalExplicit 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

PropertyTypeRequiredDescription
typeneveroptional[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.
titlestring | Record<string, string>optionalChart title
subtitlestring | Record<string, string>optionalChart subtitle
descriptionstring | Record<string, string>optionalAccessibility description — announced to screen readers as the chart’s label
xAxisneveroptional[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.
yAxisneveroptional[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.
seriesneveroptional[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.
colorsstring[] | Record<string, string>optionalColor palette (string[]) or value→color map ({ value: color })
heightnumberoptionalFixed plot height in pixels (overrides the container default)
showLegendbooleanoptional (default: true)Display legend
showDataLabelsbooleanoptional (default: false)Display data labels
annotations{ type?: Enum<'line' | 'region'>; axis?: Enum<'x' | 'y'>; value: number | string; endValue?: number | string; … }[]optionalReference lines/bands drawn over the plot: { type: "line" | "region", axis: "x" | "y", value, endValue?, color?, label?, style? }
interaction{ tooltips?: boolean; brush?: boolean }optionalInteraction toggles: { tooltips?, brush? }
arianeveroptional[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]

PropertyTypeRequiredDescription
typeEnum<'line' | 'region'>optional (default: "line")
axisEnum<'x' | 'y'>optional (default: "y")
valuenumber | string✅Start value
endValuenumber | stringoptionalEnd value for regions
colorstringoptional
labelstring | Record<string, string>optionalDisplay label — the default-language string, or an inline locale map ({ en, "zh-CN" }) resolved at render time
styleEnum<'solid' | 'dashed' | 'dotted'>optional (default: "dashed")

Nested Shape: DashboardWidgetChartConfig.interaction

PropertyTypeRequiredDescription
tooltipsbooleanoptional (default: true)Show the hover tooltip
brushbooleanoptional (default: false)Show the range selector under the plot

DashboardWidgetOptions

Widget configuration — declared query keys + open renderer extras

Properties

PropertyTypeRequiredDescription
dateGranularityEnum<'day' | 'week' | 'month' | 'quarter' | 'year'>optionalBucket selected date dimensions (day/week/month/quarter/year)
sortBystringoptionalDimension/measure name to order by
sortOrderEnum<'asc' | 'desc'>optionalSort direction for sortBy
limitintegeroptionalMax rows (applied after ordering)
stageOrder(string | number | boolean)[]optionalExplicit 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

PropertyTypeRequiredDescription
namestringoptionalStable filter name (variable key); defaults to field
fieldstring✅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)
objectstringoptionalObject whose fields.<object>.<field> translation-bundle entry resolves this filter's field label and option labels
labelstring | Record<string, string>optionalDisplay label for the filter
typeEnum<'text' | 'select' | 'date' | 'number' | 'lookup'>optionalFilter input type
options{ value: string | number | boolean; label: string | Record<string, string> }[]optionalStatic filter options
optionsFrom{ object: string; valueField: string; labelField: string; filter?: any }optionalDynamic filter options from object
defaultValuestring | number | booleanoptionalDefault filter value
scopeEnum<'dashboard' | 'widget'>optional (default: "dashboard")Filter application scope
targetWidgetsstring[]optionalWidget IDs to apply this filter to

Nested Shape: GlobalFilter.options[number]

PropertyTypeRequiredDescription
valuestring | number | boolean✅Option value
labelstring | 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

PropertyTypeRequiredDescription
objectstring✅Source object name
valueFieldstring✅Field to use as option value
labelFieldstring✅Field to use as option label
filteranyoptionalFilter to apply to source object

GlobalFilterOptionsFrom

Dynamic filter options from object

Properties

PropertyTypeRequiredDescription
objectstring✅Source object name
valueFieldstring✅Field to use as option value
labelFieldstring✅Field to use as option label
filteranyoptionalFilter to apply to source object

WidgetActionType

Widget action type

Allowed Values

  • script
  • url
  • modal
  • flow
  • api
  • form

WidgetColorVariant

Widget color variant

Allowed Values

  • default
  • blue
  • teal
  • orange
  • purple
  • success
  • warning
  • danger

On this page