ObjectStackObjectStack

Dashboard

Dashboard protocol schemas

Source: packages/spec/src/ui/dashboard.zod.ts

TypeScript Usage

import { DashboardSchema, DashboardHeaderSchema, DashboardHeaderActionSchema, DashboardWidgetSchema, DashboardWidgetOptionsSchema, GlobalFilterSchema, GlobalFilterOptionsFromSchema, WidgetActionTypeSchema, WidgetColorVariantSchema } from '@objectstack/spec/ui';
import type { Dashboard, DashboardHeader, DashboardHeaderAction, DashboardWidget, DashboardWidgetOptions, GlobalFilter, GlobalFilterOptionsFrom, WidgetActionType, WidgetColorVariant } from '@objectstack/spec/ui';

// Validate data
const result = DashboardSchema.parse(data);

Dashboard

Properties

PropertyTypeRequiredDescription
namestringDashboard 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)
gapintegeroptionalGrid gap in Tailwind spacing units
refreshIntervalnumberoptionalAuto-refresh interval in seconds
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
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; apply them 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; apply them 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
idstringUnique 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{ type: Enum<'bar' | 'horizontal-bar' | 'column' | 'line' | 'area' | 'pie' | 'donut' | …>; title?: string | Record<string, string>; subtitle?: string | Record<string, string>; description?: string | Record<string, string>; … }optionalChart visualization configuration
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; apply them 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; apply them 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; apply them by hand.
filteranyoptionalPresentation-scope filter (runtimeFilter)
compareTo{ kind: Enum<'previousPeriod' | 'previousYear'>; dimension?: string }optionalPeriod-over-period comparison window ({ kind, dimension? })
datasetstringDataset name to bind (ADR-0021)
dimensionsstring[]optionalDimension names — X/group/split
valuesstring[]Measure names — Y (at least one)
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; apply them 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; apply them 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
fieldstringField name to filter on
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.
reasonstringUser-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
actionUrlstringURL or target for the action
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
actionUrlstringURL or target for the action
actionTypeEnum<'script' | 'url' | 'modal' | 'flow' | 'api' | 'form'>optionalType of action
iconstringoptionalIcon identifier for the action button

DashboardWidget

Properties

PropertyTypeRequiredDescription
idstringUnique 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{ type: Enum<'bar' | 'horizontal-bar' | 'column' | 'line' | 'area' | 'pie' | 'donut' | …>; title?: string | Record<string, string>; subtitle?: string | Record<string, string>; description?: string | Record<string, string>; … }optionalChart visualization configuration
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; apply them 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; apply them 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; apply them by hand.
filteranyoptionalPresentation-scope filter (runtimeFilter)
compareTo{ kind: Enum<'previousPeriod' | 'previousYear'>; dimension?: string }optionalPeriod-over-period comparison window ({ kind, dimension? })
datasetstringDataset name to bind (ADR-0021)
dimensionsstring[]optionalDimension names — X/group/split
valuesstring[]Measure names — Y (at least one)
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; apply them 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; apply them 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
typeEnum<'bar' | 'horizontal-bar' | 'column' | 'line' | 'area' | 'pie' | 'donut' | …>
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
xAxis{ field: string; title?: string | Record<string, string>; format?: string; min?: number; … }optionalX-Axis configuration
yAxis{ field: string; title?: string | Record<string, string>; format?: string; min?: number; … }[]optionalY-Axis configuration (support dual axis)
series{ name: string; label?: string | Record<string, string>; type?: Enum<'bar' | 'horizontal-bar' | 'column' | 'line' | 'area' | 'pie' | 'donut' | …>; color?: string; … }[]optionalDefined series configuration
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? }
aria{ ariaLabel?: string | Record<string, string>; ariaDescribedBy?: string; role?: string }optionalARIA accessibility attributes

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 category order for funnel/pyramid stages (stored values)

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 category order for funnel/pyramid stages (stored values)

GlobalFilter

Properties

PropertyTypeRequiredDescription
namestringoptionalStable filter name (variable key); defaults to field
fieldstringField name to filter on
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 | booleanOption 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
objectstringSource object name
valueFieldstringField to use as option value
labelFieldstringField to use as option label
filteranyoptionalFilter to apply to source object

GlobalFilterOptionsFrom

Dynamic filter options from object

Properties

PropertyTypeRequiredDescription
objectstringSource object name
valueFieldstringField to use as option value
labelFieldstringField 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