Skip to main content

Default dashboards

There's one global default per app mode — a row with company_id IS NULL. Any company without its own config renders the default for its app mode.

Three defaults ship today.


Payroll

Two tabs, no inheritance.

TabEndpointFilterActionWidgets
overview (Home)GET /dashboards/payroll, param payroll_period_idgroupedSelect period_id, grouped by yearlink → run payrolla group (left column) + three charts: cost overview, YTD costs, cost by period
wallet (AccountBalanceWallet)GET /dashboards/payroll_wallet, param yearselect yearlink → run payrollone chart: wallet transactions breakdown

The period filter defaults from the payload itself (current_period.payroll_period_id), so the dashboard opens on the current period without the URL carrying a value.


EDI

One tab, no inheritance.

TabEndpointWidgets
transfer_volume (SsidChart)GET /dashboards/edi, no paramsstatCards KPI row · a group with the volume chart + a progressList · a tabbedGroup of three calendarHeatmaps (total / incoming / outgoing) · four pie charts (204 / 990 / 214 / 210 by partner)

Read this one as the worked example of dynamicCards. The KPI row has one hard-coded card (partner count) and then expands an array into the rest:

A company trading two sets gets two cards; one trading eight gets eight. Nobody edits the config when a new set appears — the SP returns it and a card shows up, titled "EDI 940" until the translation key exists.


AI Platform

One tab, no inheritance.

TabEndpointWidgets
chat_usage (TrendingUp)GET /ai/chats/usage_per_day, no paramsthree groups — tokens, chats, messages — each pairing KPIs with a daily trend

How inheritance works

A company config can pull tabs from its app-mode default. Two ways:

  • inheritDefaultTabs"prepend" puts the default's active tabs before yours, "append" after. Deduped by key.
  • $inherit placeholders{"$inherit": {"from": "default", "key": "…"}} drops one specific default tab exactly where you put it. Use this when the order matters.

The pull happens at render time, not at save time. Change a default and every inheriting config picks it up on the next page load. That's the feature, and also the risk — check who inherits before restructuring a default.

Renaming a default tab's key breaks inheriting configs

A placeholder pointing at a key that no longer exists is silently dropped. The tab just stops appearing, with no error anywhere. Update the placeholders in the same change.

To find who inherits: look for configs on that app mode whose inheritDefaultTabs isn't "none", or whose tabs contain a $inherit.


Changing a default

All of it happens in the Builder.

  1. Settings → Dashboard Builder.
  2. Open the card under Global defaults for that app mode.
  3. Edit, watch the validation chip, Save & activate.

The save follows the revision pattern: the old default becomes an inactive row you can restore any time from the revision-history drawer. That history is the backup — there's no file to keep in sync.

Defaults have the widest blast radius

Every company without its own config renders this, and every inheriting config pulls from it. Check who inherits (below) before restructuring one, and prefer isActive: false over deleting a tab.

Copying a default to another environment

Since configs live only in the database, moving one between dev, stage and prod is a copy-paste through the Builder:

Validation still runs before the save, so a config that came from somewhere else gets checked exactly like one you built by hand.

Pasted JSON skips the pickers

Icon names and permission codes are validated by the Builder's dropdowns, not by the Validator. A config pasted from another environment can reference a permission code that doesn't exist there — which silently hides the element from everyone. Spot-check permissions after a cross-environment copy.


Two rules specific to defaults

A global default can't be deactivated. POST /:id/deactivate returns 422 when company_id IS NULL. It's the fallback every company without its own config renders — turning it off would leave them with a 404.

Only one default per app mode can be active. Filtered-unique indexes enforce it, and the controller deactivates siblings before inserting or activating, so those indexes are never tripped.