Skip to content

Building dashboards in Noemata

This is the entry point for authoring frames — Noemata’s dashboards. Read it first, then follow the links below into the topic you need.

A frame is a single JSON file (*.frame.json) with two halves:

  • a view — a semantic layer over your database tables (filters, derived columns, and metrics), described in Views;
  • a page — a tree of UI blocks that query and visualize that view, described in Blocks.

Concepts explains how those two halves fit together and how data flows from a table to a chart. Cookbook has complete, working recipes you can copy.

The shape of a frame

{
"title": "Checkout latency",
"view": {
"tables": [{ "from": { "frame": "@opentelemetry_core/traces" } }]
},
"page": {
"@block/panel": {
"title": "Latency (p95)",
"@block/plot": { "marks": [{ "line": { "y": "DurationP95" } }] }
}
}
}

Only title, view, and page are required. view.tables is required (it may be empty); page may be null for a data-only frame that exists only to be reused by others.

Pages: extra views over one frame

A *.page.json file is a page with no view of its own. It renders at its own route id over the view of the nearest frame in its chain, so several pages can share a single compiled view instead of each redefining one.

{
"title": "Checkout logs",
"page": {
"@block/panel": { "title": "Recent logs", "@block/table": { "value": "Logs" } }
}
}

A page has only title (optional — it falls back to the frame’s) and page; it never declares view or params. Route params are owned by frames and inherited through the chain, so a page may only use {Param} segments an ancestor frame already declares.

  • @frames/services/foo.page.json renders at services/foo over the view of the services frame (@frames/services/index.frame.json).
  • A same-id page over a page: null frame splits one frame’s view and UI across two files: @frames/services/index.frame.json holds the view, and @frames/services/index.page.json holds the page.

It is an error for a page to sit at the same route id as a frame that already defines a page, or to live where no frame exists to render against — noemata validate reports both.

How to author and validate

The loop is the same every time:

  1. Find a base view. Don’t query raw tables. Build on the semantic layer the integrations install (for example @opentelemetry_core/traces), which already defines the columns and metrics you want. See Views.
  2. Write the frame. Add a page of blocks that query and render the view’s metrics.
  3. Validate. Run noemata validate from anywhere in the workspace. It loads every frame and reports schema and reference errors with file paths. Add --online to also render each frame against the live backend and catch runtime errors — bad SQL, missing columns, never-loading panels — that static validation can’t see. Fix and re-run until clean.
  4. Render. noemata up serves the frame; open it in the browser to confirm it looks right against live data.

Where things live

  • Your frames: anywhere under @frames/ in the workspace, as *.frame.json (and *.page.json for standalone pages).
  • Installed semantic layers and example dashboards: @frames/@<integration>/ (for example @frames/@opentelemetry_core/ and @frames/@opentelemetry/). Noemata owns these and overwrites them on every noemata up — extend them by reference, never edit them in place (see Views).
  • These docs, installed into the project: @frames/@noemata/docs/.

Reference

  • The installed OTel frames under @frames/@opentelemetry/ are the best worked examples — real, validated dashboards. Read them alongside Cookbook.
  • The JSON schemas under .data/schemas/ (frame.json, page.json, blocks.json) drive editor autocomplete and list every field and block in full.