Dashboards and Panels
Dashboard
Section titled “Dashboard”| Prop | Default | Notes |
|---|---|---|
title | required | |
uid | export name as a uid | 1-40 letters, digits, -, _ (GRAF001, GRAF106) |
description, tags | none, [] | |
time | { from: "now-6h", to: "now" } | |
refresh, timezone, weekStart, fiscalYearStartMonth, liveNow, timepicker | Grafana’s defaults | |
graphTooltip | "default" | "sharedCrosshair" or "sharedTooltip" |
editable | true | |
variables | [] | variable entities, in dropdown order |
panels | [] | panels, rows and library panels, top to bottom |
links | [] | { title, type, url?, … }; the rest of Grafana’s link fields are filled in |
annotations | [] | annotation queries: { name, datasource?, target?, … }; enable defaults to true, iconColor to red |
folder | General | a path (a subdirectory of dashboards/; "Platform/Kubernetes" nests on Grafana 13.1 and later) or a Folder, which pins the folder’s uid (see Provisioning) |
schemaVersion | the pinned schema’s | chant import sets it for a dashboard saved by an older Grafana, so Grafana still migrates it on load |
The emitted JSON carries schemaVersion from the pinned dashboard schema unless the dashboard sets its own, the declared annotations (Grafana adds its built-in “Annotations & Alerts” one on load), and no numeric id.
Annotations
Section titled “Annotations”An annotation query draws events on the dashboard’s time series panels: deploys, incidents, alert state changes. Its datasource takes whatever a panel’s does, and its target is the query in the datasource plugin’s shape or a query entity of that plugin:
export const checkout = new Dashboard({ title: "Checkout", annotations: [ { name: "Deploys", datasource: prometheus, target: new PromQuery({ expr: 'changes(kube_deployment_status_observed_generation{deployment="checkout"}[5m]) > 0' }), titleFormat: "deploy" }, { name: "Incidents", datasource: { type: "grafana", uid: "-- Grafana --" }, iconColor: "orange", target: { type: "tags", tags: ["incident"], limit: 100, matchAny: false } }, ],});Leave out Grafana’s built-in “Annotations & Alerts” query: Grafana adds it to every dashboard without one. Declare it (with builtIn: 1) only to change it, for example to turn it off. GRAF101 and GRAF102 check an annotation’s datasource like a panel’s, GRAF103 its variables, and GRAF108 the PromQL of one sent to Prometheus.
Panels
Section titled “Panels”| Class | Grafana type | Default size (w×h) | Typed from |
|---|---|---|---|
TimeSeriesPanel | timeseries | 12×8 | timeseries options and field config |
StatPanel | stat | 6×4 | stat options |
GaugePanel | gauge | 6×6 | gauge options |
TablePanel | table | 12×8 | table options and field config |
LogsPanel | logs | 24×10 | logs options |
TracesPanel | traces | 24×12 | no options schema |
HeatmapPanel | heatmap | 12×8 | heatmap options and field config |
TextPanel | text | 24×3 | text options |
BarChartPanel | barchart | 12×8 | barchart options and field config |
BarGaugePanel | bargauge | 12×8 | bargauge options |
PieChartPanel | piechart | 8×8 | piechart options and field config |
StateTimelinePanel | state-timeline | 24×8 | statetimeline options and field config |
StatusHistoryPanel | status-history | 24×8 | statushistory options and field config |
HistogramPanel | histogram | 12×8 | histogram options and field config |
NodeGraphPanel | nodeGraph | 24×12 | nodegraph options |
XYChartPanel | xychart | 12×8 | xychart options and field config |
TrendPanel | trend | 12×8 | trend options and field config |
CanvasPanel | canvas | 12×10 | canvas options |
GeomapPanel | geomap | 12×10 | geomap options |
CandlestickPanel | candlestick | 12×8 | candlestick options and field config |
AnnotationsListPanel | annolist | 8×10 | annotationslist options |
DashboardListPanel | dashlist | 8×10 | dashboardlist options |
NewsPanel | news | 8×10 | news options |
DataGridPanel | datagrid | 12×8 | datagrid options (Grafana 12.4 only) |
FlameGraphPanel | flamegraph | 24×12 | FlameGraphOptions, typed by hand |
AlertListPanel | alertlist | 8×10 | AlertListOptions, typed by hand |
Every panel takes title, description, gridPos, datasource, targets, options, fieldConfig, transformations, links, repeat (a variable or its name), repeatDirection, maxPerRow, maxDataPoints, interval, timeFrom, timeShift, hideTimeOverride, transparent, pluginVersion and id.
options and fieldConfig.defaults.custom are the panel plugin’s own types from the pinned schema, with every field optional: Grafana fills in what a panel leaves out. schema.timeseries.Options and the other namespaces under schema are exported for anyone who wants the full types.
The alert list and flame graph panels define their options in Grafana’s TypeScript, with no CUE kind, so there is no schema to generate from. AlertListOptions and FlameGraphOptions are written by hand from Grafana 13.2.2’s source and exported. GRAF107 checks these two panels, and the traces panel, against the dashboard’s Panel definition but not their options.
Transformations
Section titled “Transformations”transformations is typed per transformer. The dashboard schema only says a transformation has an id and some options, and foundation-sdk v0.0.20 has no schema for any transformer, so the option types are transcribed from the transformers Grafana v13.2.2 registers: packages/grafana-data/src/transformations/transformers/ for the standard ones, public/app/features/transformers/ for the rest. All 42 ids are covered, from organize, reduce, joinByField and calculateField to spatial and regression. Every top-level option is optional, because Grafana fills in each transformer’s defaults before it runs.
import { TablePanel, transformation, customTransformation } from "@intentius/chant-lexicon-grafana";
export const pods = new TablePanel({ title: "Pods", transformations: [ { id: "labelsToFields", options: { mode: "columns", keepLabels: ["pod", "namespace"] } }, transformation("organize", { excludeByName: { Time: true }, renameByName: { pod: "Pod" } }), transformation("sortBy", { sort: [{ field: "Pod" }] }, { disabled: false }), customTransformation("my-org-transformer", { level: 2 }), ],});An object literal is checked against its id: an unknown id, a misspelt option or a bad reducer id is a type error. There is one gap. For an option key that another transformer takes (fields belongs to reduce and groupBy, for example), tsc checks the literal against every transformer at once, so a wrong transformer’s key can slip through. transformation(id, options, common?) checks against the one transformer named, so prefer it. For a plugin’s transformer, or options newer than Grafana 13.2.2, use customTransformation(id, options, common?), which takes anything and writes it unchanged. common holds disabled, filter and topic.
Datasources on a panel
Section titled “Datasources on a panel”A query uses its own datasource, else the panel’s. The panel’s emitted datasource is its own, or the one all its queries share, or Grafana’s -- Mixed -- when they differ. A panel can also be given { type: "datasource", uid: "-- Mixed --" } itself; its queries then each use their own datasource, and one that names none goes to Grafana’s default datasource. A Row’s datasource is written on the row header only; a panel inside the row does not inherit it, as in Grafana, so a panel or query with no datasource uses the dashboard’s default.
Layout
Section titled “Layout”Grafana’s grid is 24 columns. A panel with both gridPos.x and gridPos.y is placed exactly there, and its cells are reserved before anything else is placed, wherever it is declared. Any other panel is placed by the dashboard: left to right in declaration order, wrapping to a new line when the next panel would pass column 24, using gridPos.w/h if given and the class’s default size otherwise. It skips cells already taken, so it never lands on an explicitly placed panel. A panel with only x keeps that column and goes on the first line where it is free, after the panel before it; a panel with only y takes the first free column on that line, or on the first line below with room.
A Row starts on the first free line below everything placed so far and takes the full width and one grid unit of height. With gridPos: { y } it goes on that line instead, which is reserved before anything else is placed, as an explicitly placed panel’s cells are. Its panels are placed below it. With collapsed: true they are written inside the row (Grafana’s format for a collapsed row), laid out on their own grid starting under the header, and the next item starts right under the row header.
GRAF105 reports a panel wider than the grid and any two panels that overlap.
Panel and row ids default to 1, 2, 3… in the order they appear, skipping any id set explicitly. Query refIds default to A, B, C… within each panel.
Library panels
Section titled “Library panels”A library panel is kept in Grafana’s library and shared by dashboards: each dashboard holds a reference, { id, gridPos, libraryPanel: { uid, name } }, and Grafana draws the library’s copy. LibraryPanel declares that copy:
import { Dashboard, LibraryPanel, LibraryPanelRef, PromQuery, StatPanel, TimeSeriesPanel } from "@intentius/chant-lexicon-grafana";
export const burnRate = new LibraryPanel({ name: "Burn rate", uid: "slo-burn-rate", folder: "SLOs", panel: new TimeSeriesPanel({ title: "Burn rate", targets: [new PromQuery({ expr: "slo:burn_rate:1h" })] }),});
export const checkout = new Dashboard({ title: "Checkout", panels: [ new StatPanel({ title: "Requests" }), burnRate, new LibraryPanelRef({ libraryPanel: burnRate, gridPos: { x: 0, y: 8, w: 24, h: 6 }, title: "Burn rate" }), ],});LibraryPanel prop | Default | Notes |
|---|---|---|
name | required | the name the library lists it under; references carry it too |
uid | the name as a uid | 1-40 letters, digits, -, _ (GRAF001) |
folder | the folder of the first dashboard that uses it | a path or a Folder |
panel | required | any panel; its gridPos and id are left out, since each reference has its own |
A LibraryPanel listed in panels (or in a Row’s) is laid out like a panel, at its panel class’s default size. A LibraryPanelRef sets the reference’s gridPos, id and title; Grafana shows the library panel’s own title whatever the reference says. A LibraryPanelRef can also name a library panel the project does not declare, by { uid, name }: one that already exists in Grafana, which the build references and never writes.
The build writes each library panel a dashboard places into that dashboard’s __elements, keyed by uid, as Grafana’s “Export for sharing externally” does, plus the uid of its folder when it names one. GRAF107 checks the model as a panel. What creates the library panel in Grafana depends on how the dashboard gets there:
- Apply over the API writes it to the library before the dashboards, in its folder.
- Grafana’s own import (Dashboards → New → Import, or
POST /api/dashboards/import) creates it from__elements. GrafanaOperatorResourceswrites aGrafanaLibraryPanelfor it (see Provisioning).- File provisioning, and the sidecar ConfigMaps that load the same files, do not: Grafana stores the dashboard with its
__elementsand draws the reference only once a library panel with that uid exists. Checked against Grafana 12.4.11 and 13.2.2.
Variables
Section titled “Variables”| Class | Grafana type | Key props |
|---|---|---|
QueryVariable | query | datasource, query, definition, regex, refresh ("never", "onLoad", "onTimeRangeChange"), sort |
CustomVariable | custom | values, each a value or "text : value" |
IntervalVariable | interval | values, auto, autoCount, autoMin |
DatasourceVariable | datasource | pluginType, regex |
ConstantVariable | constant | value (always hidden) |
TextboxVariable | textbox | value |
AdhocVariable | adhoc | datasource, filters, baseFilters, defaultKeys, allowCustomValue, enableGroupBy |
GroupByVariable | groupby | datasource, options, defaultValue, allowCustomValue |
SwitchVariable | switch | enabled, enabledValue (default "true"), disabledValue (default "false") |
All take name, label, description, hide ("label", "valueOnly", "hidden") and skipUrlSync, and all but the switch take current; the query, custom and datasource variables take multi, includeAll and allValue.
A QueryVariable’s query is a string, or the object the datasource’s variable editor writes, which Grafana hands to the datasource as it is. Prometheus writes { qryType, query, refId }; the PrometheusVariableQuery type lists its fields. The object form keeps the editor’s own form (label values, series query, and so on) when the dashboard is opened in Grafana; definition defaults to the query text.
const namespace = new QueryVariable({ name: "namespace", datasource: prometheus, query: { qryType: 1, query: "label_values(kube_namespace_created, namespace)" }, multi: true, includeAll: true,});An AdhocVariable adds its filters ({ key, operator, value }) to every query sent to its datasource, and a GroupByVariable adds a grouping by the keys selected; no query names them. Both ask the datasource for keys unless given defaultKeys or options. A SwitchVariable is $name set to enabledValue or disabledValue.
These three depend on the Grafana version:
| Class | Grafana |
|---|---|
AdhocVariable | every supported version; enableGroupBy is new in 13 and needs the dashboardUnifiedDrilldownControls feature toggle |
GroupByVariable | in the dashboard schema since 10.4, but experimental in 12.4 and 13.x: with the groupByVariable feature toggle off, Grafana drops the variable when it loads the dashboard. Grafana 12.4 also cannot export a dashboard that has one |
SwitchVariable | 12.3 and later |
A CustomVariable value is written the way Grafana reads it: "Production : prod" shows Production and sets prod, and a comma is part of the value (chant escapes it as \,, and leaves one already escaped alone).
A DatasourceVariable can be passed anywhere a datasource goes. The reference is written as { type: <pluginType>, uid: "${name}" }, and its type parameter keeps a PromQuery from using a Loki datasource variable.
Queries reference variables in their text, $name or ${name}. GRAF103 checks each one is declared on the dashboard; Grafana’s own $__… variables need no declaration.
A panel or row with repeat is copied once per selected value of that variable. Grafana repeats only over a query, custom, datasource or group by variable, and a query, custom or datasource one needs multi or includeAll to have more than one value; GRAF110 warns about a repeat that can only ever show one copy.