Skip to main content

Creating a new dashboard from scratch

No endpoint, no data shaped for a dashboard, nothing to copy. You're building all four layers.

If the endpoints already exist, you want Migrating an existing dashboard instead — that path is usually much shorter.


Plan first

Answer three questions before touching anything.

1. Who sees this?

One audience or several? That decides your tab structure, because tabs are how you split audiences — one config with permission-gated tabs, never two configs.

A user holding only code A never sees Tab B — and can't call its endpoint either.

2. What question does each widget answer?

If you can't say it in one sentence, cut the widget.

3. What does each tab's payload look like?

Sketch it before you write it. The payload is a contract between three layers, and getting it right on paper is much cheaper than getting it right three times in code.

Renaming a payload key breaks the dashboard silently

Widgets bound to a key that no longer exists render empty. Nothing raises, nothing appears in Grafana. That's exactly why the contract is worth writing down.


Part A — Database

1. Write one stored procedure per tab. The convention is one sp per tab.

Three rules keep these consistent:

RuleWhy
Scope everything to the companyChase the relationship down to the partners table and filter on the company id.
Date params are optional, end date inclusiveAn unset filter means unbounded, not empty.
Return one row per thing you'll chartDo the grouping in SQL. Neither the endpoint nor the config should be reshaping arrays.

2. Smoke-test every result type against a real company id, once with no date range, once with one, before anything downstream depends on the shape.

3. Create the permission codes if not already created.

4. Attach the codes to permission groups.

This is the step people miss

A permission code that belongs to no group grants nobody anything. It is the single most common reason a finished dashboard shows up empty.

Do it through our Portal Settings → Permissions as an Admin.

5. Run it all on dev. Staging and production come later, at elevation.


Part B — Backend

6. Add one route and one action per tab. The config model is one API call per tab, so each action calls the stored procedure once per result type and assembles a single response.

Four things to get right:

Do thisWhy
Bundle the whole tab into one responseKPIs, chart series, filter options, drill-through ids — one round trip per tab.
Gate with the any-of permission checkThe all-of helper would demand the tab code and the admin code. The any-of check passes on either, and short-circuits for system records.
Localize labels server-sideReturn bucket names and status descriptions already translated, so the config binds to a name field and never hard-codes UI text.
Accept the filter paramsWhatever the dashboard will send

7. Call each endpoint and keep the response open. Every $ref path you write in the next part is a path into that document.


Part C — Studio Builder

Sign in as a system administrator: Settings → Dashboard Builder → Create dashboard.

8. Guided setup asks four things:

FieldWhat to pick
NameInternal only — never shown to end users. Make it descriptive.
App modepayroll, edi, or ai_platform.
ScopeSpecific company, or Global default to replace the dashboard everyone sees.
InheritanceStart fresh when this replaces the default entirely.

9. Build the first tab. In Tab settings, give it a key (stable, lowercase — it goes in the URL), a label translation key, an icon, and the permission codes that gate it. Then, still in Tab settings:

  • Data sources — add main, point it at your endpoint, and bind each param to a filter. A dateRange filter splits into two halves addressed with dotted keys, period.start and period.end.
  • Filters — in the order you want them in the toolbar.
  • Actions — toolbar buttons. Exports are usually exportCsv.

10. Add the widgets in display order. Canvas order is render order.

PathField suggestions come from the sample fixture, so a widget renders on the canvas the moment you fill it in. Building several similar charts? Build the first one fully, then duplicate with Ctrl+D — usually only the label, paths and color differ.

The canvas renders sample data, never your endpoint

Shapes and layout are real; the numbers are fixtures. You verify against real data in Part D.

11. Add the remaining tabs. Click + and repeat. Each tab is fully independent — its own data sources, filters, actions and widgets.

12. Layout pass. Cycle the viewport toggle. Resizing edits that one breakpoint:

ViewportBreakpoint edited
Desktoplg
Laptopmd
Tabletsm
Phonexs

13. Validate, then Save & activate. Target: Valid, 0 errors, 0 warnings — click the chip to jump to any offending widget. Activating deactivates the prior config for that scope and inserts this one as a new revision; the old one stays in the history drawer.


Part D — Verify

14. Check every audience. Sign in without each gating code, then as an admin.

The tab being absent rather than hidden is the point — the Resolver strips it server-side, so there is no element in the DOM to find. A user without a tab's code should also get a 403 from that tab's endpoint.

15. Check the data and the interactions.

  • Every card and chart shows real numbers. A blank one is a wrong $ref path, not a bug, missing nodes resolve to null silently.
  • Filters change the data and update the URL.
  • Click-throughs land on real pages, with no {placeholder} left in the URL.
  • The Network panel shows one /dashboards/… call per tab.
  • Both languages render with no console errors.

16. Promote to stage and prod, in this order. The config is useless without the layers under it.

Step 4 is the only one that isn't a normal deploy: configs live in the database, so you copy the JSON out of the dev Builder and paste it into the target environment's Builder through JSON tools. Validation runs there too.

Then re-check permissions in the target environment. A pasted config can name a code that doesn't exist there yet, and the Validator won't catch it — the tab just silently disappears for everyone.

17. You don't need to file the JSON anywhere. Every save is a new row, so the revision-history drawer is the backup and Restore is the rollback. Use JSON tools → Download only if you want an off-box copy for a review or a ticket.


Checklist

Database

  • One stored procedure per tab, sliced by result type
  • Company-scoped, with an optional inclusive date range
  • Smoke-tested per result type against a real company id
  • Permission codes inserted idempotently
  • Codes attached to permission groups ← the step people miss

Backend

  • One route and one action per tab
  • One bundled response per tab
  • Gated with the any-of permission check (tab code or admin code)
  • Labels localized server-side
  • Response captured and kept open while building

Studio Builder

  • Guided setup: name, app mode, scope, inheritance
  • Tab key, label, icon, permissions
  • Data source main, params bound to filters
  • Filters and actions recreated
  • Widgets in display order, with source, grid and paths set
  • Layout pass across all four viewports
  • Validation chip reads Valid
  • Save & activate

Verify

  • Every audience sees exactly its own tabs
  • Numbers correct, filters work, click-throughs resolve
  • One request per tab
  • Both languages, no console errors
  • Promoted in order: database → backend → permission groups → config