Dashboard plugin for AdminForth.
It adds configurable dashboard pages backed by an AdminForth resource. Dashboard records define groups and widgets, the plugin renders them under /dashboard/:slug, contributes a Dashboards sidebar group, and exposes endpoints for editing groups and widgets from the AdminForth UI.
Full setup guide: https://adminforth.dev/docs/tutorial/Plugins/dashboard/
Dashboard editing is limited to superadmin by default. Set editRoles when registering the plugin to allow other roles:
newDashboardPlugin({dashboardConfigsResourceId: 'dashboard_configs',editRoles: ['superadmin','developer'],})typeDashboardConfig={version: numbergroups: {id: stringlabel: stringorder: number}[]widgets: DashboardWidgetConfig[]}Each widget has common fields:
| Field | Description |
|---|---|
id | Persisted widget id. |
group_id | Group where the widget is rendered. |
label | Optional widget title. |
target | Widget type: table, chart, kpi_card, pivot_table, or gauge_card. |
order | Widget order inside its group. |
variables | Optional widget variables passed to widget data loading. Variables are not available inside query.calcs. |
size | Preset width: small, medium, large, wide, or full. |
width, height, min_width, max_width | Optional explicit layout constraints. |
query | Data query definition. |
| Widget target | Config field | Main settings | Data usage |
|---|---|---|---|
table | table | pagination, page_size, columns | Uses query to display raw or aggregate rows. |
chart | chart | type, x, y, label, value, series, buckets, color, colors | Uses the same query shape for most chart types. Multi-resource charts use query.source: steps; add query.bucket for shared numeric buckets across resources. |
kpi_card | card | value, subtitle, comparison, sparkline | Reads the first returned query row. |
gauge_card | card | value, target, progress, color | Reads the first returned query row. |
pivot_table | pivot | rows, columns, values | Uses query rows to build a pivot table. |
Chart widget types:
| Chart type | Notes |
|---|---|
line | Uses x and y; y may contain multiple fields in config. |
pie | Uses label and value. |
bar | Uses x and y. |
stacked_bar | Uses x, y, and series. |
funnel | Uses label, value, and optional colors. Data comes from the same query shapes as every other chart. |
histogram | Uses x, y, and optional buckets. Current histogram runtime support is single-resource only: provide raw rows for the numeric field and let chart.buckets derive counts on the frontend. For multi-resource bucket distributions, use stacked_bar with query.source: steps and query.bucket. |
typeQueryConfig={source?: 'resource'resource: stringselect?: Array<|{field: string;as?: string;grain?: 'day'|'week'|'month'|'year'}|{agg: 'sum'|'count'|'count_distinct'|'avg'|'min'|'max'|'median';field?: string;as: string;filters?: DashboardFilter|DashboardFilter[]}|{calc: string;as: string}>filters?: DashboardFilter|DashboardFilter[]group_by?: Array<string|{field: string;as?: string;grain?: 'day'|'week'|'month'|'year';timezone?: string}>order_by?: Array<{field: string;direction?: 'asc'|'desc'}>limit?: numberoffset?: numberbucket?: {field: string;buckets: Array<{label: string;min?: number;max?: number}>}calcs?: Array<{calc: string;as: string}>formatting?: Record<string,JsonValue>}|{source: 'steps'steps: Array<{name: stringresource: stringselect: Array<{agg: 'sum'|'count'|'count_distinct'|'avg'|'min'|'max'|'median';field?: string;as: string;filters?: DashboardFilter|DashboardFilter[]}>filters?: DashboardFilter|DashboardFilter[]}>calcs?: Array<{calc: string;as: string}>order_by?: Array<{field: string;direction?: 'asc'|'desc'}>limit?: numberoffset?: numberformatting?: Record<string,JsonValue>}
`source: 'steps'` returnsoneaggregaterowperstepbydefault.Eachstepsupportsaggregate `select` itemsplusoptional `filters`;itdoesnotsupportper-step`field`selects,`calc`selects,or`group_by`.Add`query.bucket`whenmultipleresourcesneedthesamenumericbuckets,forexampleastackedbardistributionbypricerange.typeDashboardFilter=|{and: DashboardFilter[]}|{or: DashboardFilter[]}|{field: stringeq?: FilterValueneq?: FilterValuegt?: FilterValuegte?: FilterValuelt?: FilterValuelte?: FilterValuein?: FilterValue[]not_in?: FilterValue[]like?: FilterValueilike?: FilterValue}typeJsonValue=string|number|boolean|null|JsonValue[]|{[key: string]: JsonValue}typeRelativeDateValue={now: true}|{now_minus: `${number}${'h'|'d'|'w'|'mo'|'y'}`}typeFilterValue=JsonValue|RelativeDateValueUse filters for rolling date ranges. Do not hard-code dates for dashboards that should move with time:
query:
resource: ordersfilters:
and:
- field: created_atgte:
now_minus: 30d
- field: created_atlt:
now: trueMulti-resource queries use source: steps. Each step uses select, even if it has only one aggregate:
target: chartlabel: Average price by databasechart:
type: bartitle: Average price by databasex:
field: namey:
field: valuequery:
source: stepssteps:
- name: SQLiteresource: cars_slselect:
- agg: avgfield: priceas: value
- name: MySQLresource: cars_mysqlselect:
- agg: avgfield: priceas: valueBucketed multi-resource queries use query.bucket. The dashboard runs each step once per bucket and returns rows with label, name, resource, and the selected aggregate aliases:
target: chartlabel: Cars by price range and databasechart:
type: stacked_bartitle: Cars by price range and databasex:
field: labely:
field: countseries:
field: namequery:
source: stepsbucket:
field: pricebuckets:
- label: Budgetmax: 3500
- label: Mid-rangemin: 3500max: 7000
- label: Premiummin: 7000steps:
- name: SQLiteresource: cars_slselect:
- agg: countas: count
- name: MySQLresource: cars_mysqlselect:
- agg: countas: countCost calculation example:
target: chartlabel: Model costschart:
type: stacked_bartitle: GPT-5.4 costs by dayx:
field: dayy:
- field: input_cost
- field: output_cost
- field: cached_costquery:
resource: model_usagefilters:
and:
- field: modeleq: gpt-5.4
- field: used_atgte:
now_minus: 7d
- field: used_atlt:
now: trueselect:
- field: used_atas: daygrain: day
- agg: sumfield: input_tokensas: input_tokens
- agg: sumfield: output_tokensas: output_tokens
- agg: sumfield: cached_tokensas: cached_tokensgroup_by:
- field: used_atas: daygrain: daycalcs:
- calc: input_tokens / 1000000 * 2.5as: input_cost
- calc: output_tokens / 1000000 * 15as: output_cost
- calc: cached_tokens / 1000000 * 0.25as: cached_costDashboardPage.vue
└── DashboardRuntime.vue
└── DashboardGroup.vue
└── WidgetShell.vue
└── WidgetRenderer.vue
├── TableWidget.vue
├── ChartWidget.vue
├── KpiCardWidget.vue
├── PivotTableWidget.vue
└── GaugeCardWidget.vue
DashboardPage.vue loads a dashboard by slug, DashboardRuntime.vue renders ordered groups, WidgetShell.vue provides the widget frame and editor actions, and WidgetRenderer.vue selects the widget component by target.