Skip to main content
View as Markdown

System Sources

Data components normally bind to a table. Some do not: a runs grid, an audit log, or a global-search result list reads from a platform endpoint that is not one of your tables.

You can bind such a component inline with dataSource: { system: { endpoint: … } }. The moment two components read the same endpoint, that raw path is duplicated in your config. app.systemSources declares each endpoint once, under a name:

app.yaml
systemSources:
  - name: runs
    endpoint: /api/admin/automations/runs
  - name: failed-runs
    endpoint: /api/admin/automations/runs
    query:
      status: failed

Components then reference the name:

app.yaml
- type: data-table
  dataSource:
    systemSource: runs

Entry Properties

Property Description
name Reference name used by { systemSource: <name> }. Lowercase kebab-case, unique within the catalog.
endpoint The read endpoint to fetch rows from (e.g. /api/admin/automations/runs).
rowsKey Key of the rows array in the response envelope. Defaults to items.
idKey Key of each row's unique id. Defaults to id.
totalKey Key of the total count in the envelope. Falls back to the number of rows returned when absent.
query Static query parameters merged into every request to the endpoint.

A catalog entry is a drop-in for the inline system form — a referencing component reads exactly the same fields it would have read inline, so switching between the two never changes the component.

Two Sources, One Endpoint

query is what makes named sources worth declaring. The same endpoint with different static parameters becomes two distinct, self-describing sources:

app.yaml
systemSources:
  - name: open-tickets
    endpoint: /api/admin/tickets
    query:
      status: open
  - name: closed-tickets
    endpoint: /api/admin/tickets
    query:
      status: closed

Each is then one word at the binding site, and the filter lives in one place rather than being restated in every component.

Which Components Accept One

Any data-bound component: data-table, list, gallery, kanban, calendar, chart, kpi and data-timeline. See Data Components for what each renders.

Cursor Feeds

Some platform endpoints paginate by cursor rather than by page number: each response carries a token for the rows after the ones it returned, and reports no total. Automation runs, the audit log and agent conversations all read this way — a feed still being written to has no stable count to report.

A grid bound to such a source gets a different surface from the numbered pager:

Page-numbered source Cursor feed
Numbered pager, previous and next A single Load more control
Each page replaces the one before Each page is appended below the rows already on screen
An x of N total No total, and no x of N

This holds even when the component declares pagination. A pageSize still sets how many rows each request asks for, but the pager it would otherwise draw is not rendered and no total is shown. That is not a gap to work around: a cursor endpoint never reports how many rows exist, so any x of N on screen would be a number the server never sent — and an operator reads a displayed total as a count.

Appending rather than replacing follows from the same shape. A cursor only moves forward, so swapping the first page out for the second would put rows the reader has already seen out of reach for the rest of the session.

Changing the search term, the sort or a filter starts a different sequence, so the accumulated rows and the token are both dropped and the feed restarts from the top. Load more carries the active search and sort into the next request, so a continuation never silently widens back to the unfiltered feed.

Page Size

query can set the page size for the endpoint itself:

app.yaml
systemSources:
  - name: recent-runs
    endpoint: /api/admin/automations/runs
    query:
      limit: '5'

A limit declared this way wins over the component's own pagination.pageSize. Naming it on the source is the author saying what a page of this endpoint is, which is the more specific statement — and it applies to every component bound to the source, so two grids reading the same feed cannot disagree about how much they ask for.

Leave limit out and the component's pageSize is used instead.

Validation

  • The catalog must declare at least one source when present.
  • Every name must be unique — a duplicate would make a reference ambiguous.
  • Every { systemSource: <name> } reference must point at a declared entry.

All three are checked when the config is decoded, so sovrium validate catches a typo'd reference offline, before the app boots.

Last updated September 1, 2026

This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.

Built with Sovrium