You're reading docs for Nextly Alpha. APIs may change between releases.

Dashboard Widgets

Dashboard Widgets

Put the numbers your team actually checks on the admin dashboard.

The admin dashboard is built from widgets. Each one declares what it wants to know, and the system runs that query on its behalf, scoped to the permissions of whoever is looking.

Availability

Built inThe widget system, the built-in sources and admin rendering all ship
Plugin-contributed widgetsExperimental. They render, but the contribution shape is still settling — see the stability ladder
PermissionA widget's query runs scoped to the viewer, so two people can see the same dashboard and different numbers

Archetypes

A widget declares an archetype rather than drawing itself, so dashboards stay visually consistent no matter who contributed the widget:

ArchetypeShows
metricOne number
statsSeveral labelled, clickable numbers
tableRows and columns
listAn ordered list of items
actionsButtons for things to do
textProse you wrote, as markdown
customYour own component, when none of the above fit

A text widget is its prose. It declares content instead of a query, and the host draws it: headings, paragraphs, lists, emphasis, inline code, block quotes and links.

{
  id: "acme/release-notes",
  title: "Before you release",
  archetype: "text",
  defaultSize: "md",
  content: [
    "## Checklist",
    "",
    "Drain the queue, then read [the runbook](https://example.com/runbook).",
  ].join("\n"),
}

Three things the markdown cannot do, by design. Raw HTML is shown as the text it is. A link may point at http, https, mailto, tel, or a path on this site written as /..., ./... or #...; a link to anything else — a bare posts?status=draft, an address with no scheme, or a path a browser would read as another site, such as /\example.com — is left on screen as the markdown you wrote it in, so you can see it was refused. And the content is bounded — it travels inside every dashboard load for every reader who is offered the card, so it is for notes and a pointer to a runbook, not for the runbook itself. An external link opens in a new tab.

Sizes are sm, md, lg, xl and full; heights are short and tall. On the dashboard, cards sit in one ordered list, filling rows left to right and wrapping, and each card is one of five widths: sm is a quarter of the row, md a third, lg a half, xl two thirds and full the whole row. A card starts at the widget's defaultSize, and a reader can change its width from the card's menu to any size between the widget's minSize and maxSize (every size, when the widget declares neither). Widths follow the dashboard's own width rather than the window's: below 1,024 pixels a quarter, third or half card is drawn at half width and a wider one at full width, and below 640 pixels every card is full width. Below the admin's md breakpoint the dashboard is read-only: arranging it is offered from md up, and the arrangement made there applies at every width.

Declared, not fetched

A widget does not run its own database query. It declares one, and the system executes it. That is what makes permission scoping reliable rather than something each widget author has to remember: a widget cannot read what its viewer could not have read directly.

It also means most widgets need no data-fetching code at all.

Sources

A query names a source, and the source decides what the query may ask:

SourceReadsSupports
collection:<slug>The collection's entries, through the same access-controlled read as the APIcount, list, groupBy, and timeseries where the collection has a usable date
single:<slug>The single's one document, as a list of one rowlist only — a single answers a fixed question, so a where, a sort, a count or a bucketing over it is refused rather than answered
system:…A core service — versions, releases, activity and the like — each answering through its own service's rulesWhatever that source declares

Every collection and every single the install has is published as a source, and the dashboard offers a card derived from each: a count, a recent list, a table and a health breakdown per collection, and one status card per single showing whether it is published and when it last changed. Offered, never placed — nobody's dashboard gains a card because a collection was created — and only to a reader who may read that collection or single.

Contributing a widget from a plugin works today, but PluginAdminWidget is marked @experimental and its shape may change before it settles. Building on it is reasonable; depending on it in something you cannot easily update is not.