Metadata Schema
Define dashboards, variables, widgets, and default layout in SolidX module metadata.
Dashboard definitions are authored in module metadata under the root-level dashboards key.
Mental Model
The dashboard schema has a small set of core players:
- The dashboard definition describes the screen itself.
- Variables describe the inputs a user can use to filter or parameterise the dashboard.
- Widgets describe the individual building blocks on the page.
- Provider bindings connect those widgets to backend data.
- The default layout describes how the widgets should initially be arranged.
Placement in Metadata
The dashboards block is not a nested widget configuration area or a UI-only document. It is a first-class module metadata section and should be treated as part of the framework contract for the module.
dashboards should be a peer of sections such as:
actionsmenusrolesusers
Minimal Dashboard Definition Shape
This is the smallest useful mental model for a dashboard definition. It gives the framework enough information to identify the dashboard, authorize access to it, and render a widget canvas that can later be expanded.
{
"dashboardSchemaVersion": 1,
"name": "queue-health",
"displayName": "Queue Health Dashboard",
"description": "Operational visibility for queue processing.",
"moduleUserKey": "solid-core",
"variables": [],
"widgets": [],
"defaultLayout": {
"engine": "gridstack",
"columns": 12,
"items": []
}
}Variable Authoring
Variables define the inputs the dashboard accepts from the user. They are not just UI controls. They become part of the runtime payload sent to backend providers, so naming and semantics should be chosen carefully.
Supported variable patterns used by the framework today:
dateselectionStaticselectionDynamicisMultiSelectfor selection variables
Date Variable
Use a date variable when widgets need a shared time window. In the current UI, this renders as a preset selector with an optional custom date range.
{
"name": "date",
"label": "Created At",
"type": "date",
"required": true
}The UI supports preset selection (today, last_24_hours, last_7_days, last_30_days) and custom date range.
Static Selection Variable
Use a static selection variable when the option set is known at design time and does not require backend lookup.
{
"name": "stage",
"label": "Stage",
"type": "selectionStatic",
"isMultiSelect": true,
"selectionStaticValues": [
"pending:Pending",
"failed:Failed",
"succeeded:Succeeded"
]
}Dynamic Selection Variable
Use a dynamic selection variable when the option list depends on live data and should be resolved through the existing selection provider pattern.
{
"name": "queueName",
"label": "Queue",
"type": "selectionDynamic",
"selectionConfig": {
"providerName": "MqDashboardQueueNameVariableOptionsProvider",
"providerContext": {
"labelField": "name",
"valueField": "name"
}
}
}selectionConfig.providerContext is passed to the dynamic selection provider and is useful when the provider needs hints about how to shape the output. For example, a provider can use it to decide which field should become the display label and which field should become the persisted value.
Widget Authoring
Widgets are the runtime units of a dashboard. Each widget definition tells the framework what kind of widget it is, which backend provider should be invoked, and any provider-specific context needed to resolve the data.
Each widget binds to a backend provider using dataProvider.
{
"id": "kpi-total-messages",
"name": "Total Messages",
"type": "kpi",
"dataProvider": "MqDashboardTotalMessagesKpiProvider",
"providerContext": {
"metric": "count_total_messages"
}
}providerContext is a metadata-authored input passed through to the provider at runtime. It is useful for keeping provider classes reusable. For example:
- A KPI provider can use
providerContext.metricto decide which aggregate to compute. - A chart provider can use
providerContext.bucketto choosehourvsday. - A table provider can use
providerContext.columnsandproviderContext.sortto shape the output.
For custom frontend rendering, add componentName.
{
"id": "queue-sla-heatmap",
"name": "Queue SLA Heatmap",
"type": "customChart",
"dataProvider": "MqDashboardQueueSlaHeatmapProvider",
"componentName": "QueueSlaHeatmapWidget"
}This is the recommended pattern when the widget still follows the standard backend provider contract, but needs a specialised visual treatment in the UI. The queue SLA heatmap reference implementation is the canonical example of this approach.
Default Layout Authoring (Gridstack)
The default layout defines the initial visual arrangement of widgets before any user-specific override is saved. The current layout engine uses a Gridstack-compatible contract.
{
"engine": "gridstack",
"columns": 12,
"items": [
{ "widgetId": "kpi-total-messages", "x": 0, "y": 0, "w": 3, "h": 2 },
{ "widgetId": "chart-stage-distribution", "x": 3, "y": 0, "w": 6, "h": 4 },
{ "widgetId": "table-recent-failures", "x": 0, "y": 4, "w": 12, "h": 4 }
]
}Navigation Integration (Actions + Menus)
Dashboards should be discoverable through the same action/menu metadata model used elsewhere in SolidX.
Dashboards should be exposed via metadata-driven menu/action pairs.
Recommended pattern:
- Action type
custom - Action path matching dashboard route
- Menu item referencing the action
The action route should follow the canonical dashboard route pattern:
/admin/core/:moduleName/dashboard/:dashboardName
Example:
{
"actions": [
{
"name": "solid-core-queue-health-dashboard-view",
"displayName": "Queue Health Dashboard",
"type": "custom",
"customComponent": "/admin/core/solid-core/dashboard/queue-health"
}
],
"menus": [
{
"name": "solid-core-queue-health-dashboard-menu-item",
"displayName": "Queue Health",
"actionUserKey": "solid-core-queue-health-dashboard-view"
}
]
}Widget Permissions
Dashboard widget permissions use SolidX's existing explicit permission mechanism. This means dashboard authors must add the required permission entries to module metadata and then assign them to the appropriate roles, just like any other explicit SolidX permission.
The dashboard widget permission format is:
dashboard:<dashboard-name>:<widget-pattern>
Where:
- The first segment is always
dashboard - The second segment is the dashboard name
- The third segment is the widget matcher
The widget matcher supports:
- Exact widget names
*for all widgets in a dashboard- Regex-style patterns in the widget segment
Examples from the queue-health reference implementation:
{
"permissions": [
"dashboard:queue-health:kpi-.*",
"dashboard:queue-health:chart-queue-.*",
"dashboard:queue-health:chart-processing-latency-trend"
]
}This means:
dashboard:queue-health:kpi-.*authorizes all KPI widgets such askpi-total-messagesdashboard:queue-health:chart-queue-.*authorizes queue-focused charts such aschart-queue-wise-failures,chart-queue-wise-avg-elapsed, andchart-queue-sla-heatmapdashboard:queue-health:chart-processing-latency-trendauthorizes one specific widget only
Any widgets not covered by the user's effective permission set will remain visible in the dashboard layout, but they will not resolve data.
Permission Runtime Behavior
The permission check is enforced on the backend first.
When a user does not have permission for a widget:
The backend does not invoke that widget's data provider at all.
The backend returns a normalized unauthorized widget envelope.
The frontend still renders the widget card in the grid.
The widget body shows a compact Unauthorized state.
This behavior is intentional because it preserves layout consistency while keeping protected widget data entirely server-side.
To make dashboard permissions effective in a real project:
Declare the explicit permission strings in module metadata.
Attach those permissions to one or more roles.
Assign those roles to users.
That flow is standard SolidX RBAC behavior and dashboards follow the same model.
Authoring Checklist
Define the dashboard in dashboards[].
Define variables with clear names and labels.
Bind each widget to a valid backend dataProvider.
Define a complete defaultLayout for all widgets.
Add action and menu entries for discoverable navigation.
Add explicit dashboard widget permissions and bind them to the right roles.

