Widgets
A widget is a card on the Cockpit dashboard. There are two main renderers, selected by component, and a third, AppPanel, that draws a panel whose answer your App computes (the inventories App ships one):
| Component | Renders |
|---|---|
EntityCount | A numeric KPI. |
EntityList | The top N rows. |
Any other component value, a typo included, renders as a count card: the dashboard does not reject it, so a misspelt EntityList shows up as a number where you expected rows. Check the card's shape after a deploy. The renderer also reads a type of count, list or panel when component is absent, but component is still the key to set.
The dashboard's unknown widget placeholder appears only when a merchant's saved dashboard still holds a widget whose App has since been uninstalled.
Both renderers
"widgets": [
{
"id": "suppliers-total",
"component": "EntityCount",
"title": { "en": "Suppliers", "de": "Lieferanten" },
"size": "small",
"position": 10,
"refresh_interval": 300,
"entity": "suppliers",
"filters": { "status": "active" },
"link_to": "/suppliers"
},
{
"id": "suppliers-recent",
"component": "EntityList",
"title": { "en": "Recently added" },
"size": "large",
"position": 20,
"entity": "suppliers",
"limit": 5,
"sort": { "column": "created_at", "direction": "desc" },
"columns": [
{ "name": "code", "label": "Code", "type": "text" },
{ "name": "name", "label": { "en": "Name" }, "type": "text" },
{ "name": "status", "label": "Status", "type": "badge", "vocabulary": "suppliers.statuses" }
],
"link_to": "/suppliers"
}
]
Every key
| Field | Applies to | Meaning |
|---|---|---|
id | Both | Required. Lowercase, hyphenated. |
component | Both | EntityCount or EntityList. |
title | Both | The card heading. Localisable. |
size | Both | small, medium (default), large or full. |
position | Both | Sort order across all installed apps' widgets. |
refresh_interval | Both | Auto-refresh in seconds. Minimum 5. |
entity | Both | The entity the widget reads, as a bare name. |
filters | Both | Filters merged into the query. Values may be bare (equality shorthand) or operator-prefixed, such as ilike.*foo*. |
limit | EntityList | Rows to fetch. Default 5. |
sort | EntityList | { column, direction }. |
columns | EntityList | The column projection. Same shape as a list view's columns, including the 26 types. |
link_to | Both | The Cockpit route the header and "view all" navigate to. Same studio-relative path semantics as a navigation entry. |
Declare sort on anything called "recent"
sort, an EntityList shows whatever the database returned first — which is physical row order, not time order. A "latest suppliers" card titled by time and ordered by nothing is a card that quietly lies.This is the same rule as on a list view, and it bites harder here because a dashboard card carries no visible sort control to correct it.
Guidance
Two or three widgets, not ten. The dashboard is shared across every installed app, exactly like the sidebar. position orders your cards among everyone else's.
Point link_to somewhere useful. A count nobody can drill into is a number without a next step.
Use filters to make the number mean something. "Suppliers" is a count of rows; "Active suppliers" is a fact an operator can act on.
Set refresh_interval deliberately. Every card that refreshes is a query per interval per open dashboard. 300 seconds is fine for a count; 5 is almost never justified.
Give a badge column a vocabulary. A hard-coded status map cannot show a value the merchant added after you shipped.
Where to go next
- List views — the column shapes
EntityListreuses, and KPIs, which are the in-page equivalent ofEntityCount. - Navigation — the studio-relative routes
link_tofollows. - Analytics Studio — where reporting and charts live.