Dashboards
Dashboards are configurable, widget-based views over your data. Each dashboard belongs to an application and is rendered from a stored layout of widgets — charts, KPIs, embedded tables, and text. A single widget can read from a different application, so one dashboard can combine data from CRM, ITSM, and HR without weakening per-application security.
How dashboards work
Section titled “How dashboards work”A dashboard is a record in the system.dashboards table, identified within its application by a slug. Its layout is a JSON document describing the grid and its widgets. Every widget’s query runs through the same authorized read path as the rest of the platform, so row-level security, column masks, and application scope always apply — a viewer only ever sees the rows they are allowed to see.
GET /api/apps/{app}/dashboards # list dashboards in an appGET /api/apps/{app}/dashboards/{slug} # get one dashboard's layoutPATCH /api/apps/{app}/dashboards/{slug} # update the layoutGET /api/apps/{app}/dashboards/{slug}/data # run every widget query, get resultsPOST /api/apps/{app}/dashboards/{slug}/data # same, with filter parameters in the bodyWidgets
Section titled “Widgets”A widget declares what to render and where to render it. Common widget types include chart, page_embed, table_embed, text, and divider. Each data-bound widget carries an inline query (a table, filters, aggregations, group-by, sort, and limit) or references a named data source.
{ "id": "open-deals", "type": "chart", "title": "Open deals by stage", "inline_query": { "table": "deals", "filters": "eq(status, open)", "aggregations": [{ "function": "count", "alias": "count" }], "group_by": ["stage"] }, "position": { "x": 0, "y": 0, "w": 6, "h": 4 }}Widgets can also declare KPI targets with red/amber/green thresholds, drilldown behavior on click, and a visible_to list to scope a widget to specific roles.
Filter parameters
Section titled “Filter parameters”A dashboard can define parameters — a filter bar shared across widgets (enum, date-range, text, or number-range). Widgets opt in by listing parameter names in their query. When you POST to the /data endpoint with parameter values, every participating widget re-runs with those filters applied.
Cross-app widgets
Section titled “Cross-app widgets”By default a widget reads from the dashboard’s own application. Set a widget’s data_app to read from a different application instead:
{ "id": "incidents-this-week", "type": "chart", "title": "Open incidents", "data_app": "itsm", "inline_query": { "table": "incidents", "filters": "eq(status, open)" }, "position": { "x": 6, "y": 0, "w": 6, "h": 4 }}Cross-app dashboards are built by combining several independently-authorized single-app reads — never by a cross-application join. Each widget resolves its own application’s permissions and RLS, and the results are combined for display. This keeps every source’s security enforced by construction:
- A viewer who can read the widget’s data app sees its data, filtered by that app’s row-level security.
- A viewer who cannot read that app gets a per-widget “forbidden” indicator — the rest of the dashboard still renders. There is no all-or-nothing failure.
- Single-app dashboards are unaffected: a widget with no
data_appbehaves exactly as before.
Reusable data sources
Section titled “Reusable data sources”Instead of embedding a query in each widget, you can define a named data source and point widgets at it by name. Data sources are stored in the system.data_sources table and executed on demand:
POST /api/apps/{app}/data-sources/{name}/executeA data source returns results in the same shape as an inline widget query, so the same definition can back multiple widgets across multiple dashboards.
Related
Section titled “Related”- Permissions — how row-level security shapes what each viewer sees
- Tables and Field Types — the data widgets query
- Applications — the boundaries cross-app widgets read across