Grafana Dashboards
Dashboards are JSON: panels on a 24-column grid, fed by data-source queries, shaped by client-side transformations, styled by standard options and overrides, parameterized by template variables, connected through annotations and links. Data shape drives the visualization; variables make dashboards reusable; links carry context across navigation.
Scope
- In scope: dashboard JSON (Classic and V2 schemas), panel selection and configuration, standard options, thresholds, value mappings, field overrides, transformations, all variable types, repeating panels/rows/tabs, annotations, dashboard/panel/data links, library panels, version history, panel editor and inspector.
- Out of scope:
- Data source query languages —
promql,logsql,metricsqlare sibling skills. This skill governs how queries wire into panels, not query syntax. - Grafana Alerting — alert rules, contact points, notification policies belong to the
alertingsibling skill. Panel-attached alert rules and the panel editor's Alert tab are integration points only. - Provisioning, file-based dashboards, dashboards-as-code tooling — Terraform, Grizzly, Foundation SDK, Helm
charts,
provisioning/dashboards/*.yamlbelong to theprovisioningsibling skill. This skill covers the JSON those tools emit.
- Data source query languages —
References
- Dashboard JSON model (Classic + V2) — [
${CLAUDE_SKILL_DIR}/references/json-model.md] Top-level fields, panel object structure,gridPos,timepicker/templating/annotationsblocks, library panel JSON shape, V2 Resource kinds, schema versioning, direct JSON edit mechanics. - Panels and visualizations — [
${CLAUDE_SKILL_DIR}/references/panels.md] Visualization-selection rules, panel-type catalog withtypeIDs, panel editor and inspector structure, library panel lifecycle, panel styles. - Panel deep dives (time series, stat, table) — [
${CLAUDE_SKILL_DIR}/references/panel-deep-dives.md] Per-panel configuration: time-series graph styles (line width, fill opacity, gradient mode, interpolation), axis placement, legend, tooltip, special overrides (Transform, Fill below to); stat value options, text modes, color and graph modes; table cell types, column filtering, sorting, tooltip-from-field, footer calculations. - Panel options — [
${CLAUDE_SKILL_DIR}/references/panel-options.md] Standard options (unit, min/max, decimals, color scheme, display name), thresholds, value mappings, field overrides, override precedence, display-name expressions. - Variables — [
${CLAUDE_SKILL_DIR}/references/variables.md] All variable types, syntax, format options, selection options, multi-value interpolation, multi-property variables, chained variables, regex filtering, global built-ins, URL sync, repeating panels/rows/tabs. - Transformations — [
${CLAUDE_SKILL_DIR}/references/transformations.md] Full catalog grouped by purpose (adding fields, filtering, reshaping, grouping, histograms, time-series modeling), common patterns, debugging mechanics. - Annotations and links — [
${CLAUDE_SKILL_DIR}/references/annotations-links.md] Native annotations, annotation queries, time regions, dashboard/panel/data links, data link variables, cross-dashboard navigation patterns. - JSON surgery — [
${CLAUDE_SKILL_DIR}/references/json-surgery.md] Verified jq recipes for dashboard files: structure/panel/variable mapping, targeted mutation, extract → edit → splice, add/delete panels, row-nesting traversal, validation, V2 element access, gcx/mcp-grafana handoff for live instances.
Working with dashboard files
Dashboard JSON runs thousands of lines. Never read a whole dashboard into context, and never rewrite one wholesale —
work surgically with jq. Full recipe catalog: json-surgery.md.
Map first. Build a structural picture without reading the file:
jq -r '(.panels[], .panels[].panels[]?) | [.id, .type, .title] | @tsv' dash.jsonEdit surgically. Single field → targeted jq mutation on the selected path. Substantial panel work → extract the panel to a small file, edit that, splice it back. Select panels by
.id, never by title.Validate.
jq emptyfor syntax, then a semantic diff against the pre-edit copy (diff <(jq -S . before) <(jq -S . after)) — it must show exactly the intended change and nothing else.
The load-bearing detail is row nesting: collapsed rows nest their panels in row.panels[], expanded rows keep them as
top-level siblings. (.panels[], .panels[].panels[]?) covers both — plain .panels[] silently misses collapsed-row
panels. For dashboards in a live Grafana instance rather than files, gcx (see provisioning) patches server-side and
the mcp-grafana MCP tools provide summary/property/patch access without full JSON.
Dashboard structure
Six top-level concerns:
- Identity —
uid(stable; externalize references via this),title,tags,version,schemaVersion. Never hand-editschemaVersionorversion. - Time —
time.from/time.to,timezone,refresh,timepicker(refresh intervals + quick ranges). templating.list— template variables. Array order = render order left-to-right.annotations.list— annotation queries. Built-in-- Grafana --query is always present.panels— panel objects. Layout viagridPos(24 columns × 30px per row unit).links— dashboard-level links (top of dashboard or controls menu).
Two schemas. Classic is the historical default and what grafana.com dashboards use. V2 Resource
(Kubernetes-style apiVersion: dashboard.grafana.app/v2beta1) supports advanced layouts and conditional rendering and
is the preferred format for new dashboards-as-code. See json-model.md for both schemas and the full panel object spec.
Panel selection
Match data shape to visualization. Grafana's auto-suggestion reads shape, not intent — don't accept it blindly.
- Time-stamped numeric series → Time series
- Categorical numeric (one value per category) → Bar chart
- Distribution of values → Histogram (point-in-time) or Heatmap (over time)
- Discrete state changes over time → State timeline
- Periodic state snapshots → Status history
- Headline number with optional sparkline → Stat
- One value vs. min/max (radial) → Gauge
- Multiple values vs. min/max → Bar gauge (gauges scale poorly past 2–3 metrics)
- Proportional parts of a whole, segment count ≤ 7 → Pie chart; for ranking use Bar chart
- Tabular rows → Table; use the
sparklinecell type with the Time series to table transformation for per-row trends - Sequential numeric x that is not time → Trend
- Arbitrary x/y scatter → XY chart
- Logs/traces/profiles/geospatial/graph → Logs / Traces / Flame graph / Geomap / Node graph
- Markdown, HTML, or RSS → Text / News
Panel type IDs in JSON: timeseries, stat, gauge, bargauge, barchart, histogram, heatmap, piechart,
table, logs, nodeGraph, traces, flamegraph, canvas, geomap, text, news, dashlist, alertlist,
annolist, state-timeline, status-history, candlestick, trend, xychart.
Panel configuration
Display pipeline order:
- Queries return data frames.
- Transformations reshape frames (client-side, ordered).
- Standard options apply per-field (unit, min/max, decimals, color scheme, display name).
- Value mappings translate values to display text and color.
- Thresholds drive conditional colors (and lines/regions on supporting panels).
- Field overrides apply per-field property changes. Last write wins.
- Visualization renders.
Standard options rules
- Always set base units. Pick from the catalog (
Time → seconds,Data → bytes,Throughput → reqps). Don't pre-scale in queries — let Grafana scale for display. - For seconds-since-epoch timestamps, multiply by 1000 with
Add field from calculation→Binary operationbefore applying aDate & timeunit. Grafana's date/time formats expect milliseconds. Stringunit displays all decimals when you need exact values without rounding.- Color scheme by intent:
- Categorical / series-name-stable →
Classic palette - Series names change run-to-run →
Classic palette (by series name) - Value-driven →
From thresholds (by value)orSingle/Multiple continuous colors (by value)
- Categorical / series-name-stable →
No value— set a placeholder (-,N/A,0) so blank panels are unambiguous.
Thresholds rules
- Defaults:
Base = green,80 = red,Mode = Absolute. Override per panel — don't ship the default unchanged. Absolutewhen threshold values are real-domain numbers.Percentageonly when comparing to dynamicmin/max.- On time-series-like panels, set
Show thresholdsexplicitly (As lines,As filled regions) — default isOff. - Table panels apply threshold colors only when cell display mode is
Color textorColor background.
Value mappings rules
- Mappings bypass unit formatting for matched values. Don't combine a mapped value with a unit suffix and expect both.
- Order matters. Grafana checks mappings top-to-bottom; first match wins.
Special → Null→ "N/A" to make missing data unambiguous in Stat/Table.
Field overrides rules
- Match by name (exact), regex, type, query refId, or values (reducer condition).
- Don't rename via override.
Display namechanges the rendered label only; for actual field renaming use Rename by regex. - Precedence:
defaults → standard options → value mappings → thresholds → overrides. Reorder in the Overrides tab when later rules need to win.
Transformations
Run in listed order; each step sees the previous output. Text fields accept dashboard variables (interpolated before execution).
- Default order for "shape and aggregate":
Filter data by values/Filter fields by name— drop unneeded data first.Convert field type/Extract fields— coerce types.Add field from calculation— derive fields.Group by/Reduce— aggregate.Organize fields by name— final naming/ordering.
- Always wrap counters in
rate()orincrease()at the query level before transformations —Reduceon raw counters is meaningless. (Query-language concern; seepromql.) Join by fieldonTime(Outer join, time series) aligns multiple queries before visualizing as Table or computing cross-query expressions.Partition by valuesinstead of N queries with differentWHEREclauses.Time series to table transformfor "row per entity, sparkline in the last column" tables.- Debug with the bug icon (input vs. output frames) or eye icon (disable to bisect). Toggle Table view in the panel preview to see raw post-transformation frames.
- Remove unused transformations. Disabled-but-present steps still cost CPU.
Variables
Variables interpolate before queries reach the data source, with data-source-specific escaping (regex for
Prometheus/InfluxDB, Lucene for Elasticsearch, glob for Graphite). Use ${var:raw} to bypass.
Variable type selection
- Query — options from a data-source query. Default for label/tag/host enumeration.
- Custom — static list (CSV or JSON). Faster than a query for values that never change.
- Text box — free input. For high-cardinality fields you can't enumerate.
- Constant — hidden, fixed value. For path prefixes that change on export/import.
- Data source — switch a dashboard's data source instance (multi-environment dashboards).
- Interval —
1m,1h,1dtime spans. Reference as$myintervalin group-by/summarize. - Filters (formerly ad-hoc) — auto-applied key/value filters. Prometheus, Loki, InfluxDB, Elasticsearch, OpenSearch, and the special Dashboard data source.
- Switch — two-state toggle for conditional query logic.
Variable rules
$varnamefor standalone references;${varname}to interpolate inside a larger expression. Never use deprecated[[varname]].Refresh: On time range changeonly when the variable query depends on time. OtherwiseOn dashboard load— saves a refetch per range change.- Order variables by dependency: parents before children. Grafana renders dropdowns left-to-right in list order.
Custom all valueis not escaped — match the data source's syntax:.+or.*for Prometheus/Loki regex;*for Graphite glob;(.*)for an explicit regex group.- Use
$__rate_intervalfor Prometheusrate()— handles the 4× scrape-interval rule automatically. - For ms-epoch URLs, use
${__from}/${__to}. For ISO, use${__from:date:iso}. For UTC, use:dateor:date:iso(other formats use browser time). - Multi-property variables (custom JSON, some query types) — define once, reference properties as
${var.aws},${var.azure}. Prefer over parallel variables for the same concept. skipUrlSync: truewhen the variable shouldn't be URL-overridable (sensitive values, shared dashboards). Constants default to this.- Repeating panels — set
Repeat by variableto a multi-value variable.Horizontalpanels can't share a row with non-repeating panels;Verticalmatches the source panel's width. Put the variable in the panel title (CPU — $host) for clarity.
Annotations
- Native annotations added via click in time series, state timeline, candlestick panels (Ctrl/Cmd-click for a point; Ctrl/Cmd-click-drag for a region). Stored in Grafana's database.
- Annotation queries fetch events from any data source. Configured in
Settings → Annotations. Each query has its own toggle, color, andShow in: All panels / Selected panels / All panels exceptscope. - Built-in
-- Grafana --query is always present. By default filters bydashboardId— Save As does not carry over manual annotations. To survive duplication, tag annotations and use a tag-filtered query. - Tag-filtered annotation queries on externally-shared dashboards leak annotations from all dashboards in the organization. Audit before enabling external sharing on a dashboard with tag filtering.
- Time regions shade recurring windows (business hours, weekends). Configure via
Query type: Time regions; supports Cron syntax in the Advanced toggle.
Links
Three link types, three scopes.
- Dashboard links (Settings → Links) — top of dashboard or controls menu. For navigation between dashboards that share variables, or "Dashboards with tag X" dropdowns.
- Panel links (Panel options → Panel links) — header-icon URLs per panel. For "open the upstream runbook" or "open source system." Cannot reference field/value variables.
- Data links (Data links and actions) — per-field click targets including value, series, and labels under the cursor. For drilldowns and panel-to-panel filtering.
Data link rules
__url_time_rangedoes not auto-insert?or&— build the query string yourself.${__all_variables}appends every current template variable. Place before any explicitvar-<name>=...so explicit values override the wildcard.- Linking to another dashboard — target must have a variable with the same name (not label). Otherwise map
explicitly:
&var-host=${server}. __field.displayNamewhen fields have been renamed;__field.namefor the raw name.- Actions trigger unauthenticated browser-originated API calls. Treat URL and body as user-visible. Gate the target API with its own auth or proxy through short-lived tokens.
One clickskips the context menu; only one data link per panel can have it enabled.
Library panels
Reusable panel definitions referenced by libraryPanel: { uid, name } instead of inline config.
- Create from a panel →
More → New library panel. Source panel becomes a library-panel reference; save the dashboard to persist. - Edits propagate to every instance on next load.
- Unlink breaks the binding for one instance (restores inline config).
- Replace swaps one library panel for another at a specific instance.
- Permissions follow the parent folder (and explicit RBAC overrides where enabled).
- Delete only when no dashboards reference the panel.
- Use library panels for the 2–3 panels you want identical everywhere (single uptime stat, standard request-rate graph). Don't libraryize panels you'll specialize per dashboard.
Version history
- Grafana keeps the last 20 versions by default. Override with
[dashboards] versions_to_keep. Settings → Versions— pick two andCompare versionsfor text + JSON diff.Restorecreates a new version with old content — old versions remain in history.- JSON Model tab in Settings edits raw JSON. Saving runs schema migration if needed.
Application
When writing dashboards:
- Pick panel type from data shape, not Grafana's auto-suggestion.
- Always set units; let Grafana handle scaling.
- Add at least one threshold on health-signaling panels; ship
Show thresholds: As linesorAs filled regionson time-series-like panels instead of the defaultOff. - Use variables for environments, namespaces, services — anything you'd otherwise duplicate. Order so dependencies render left to right.
- Use
$__rate_interval(Prometheus) and time-aware globals ($__interval,$__from,$__to) instead of hard-coded windows. - Use transformations sparingly — push aggregation into the query when the data source supports it; transformations shape after the fact.
- Apply conventions silently. If an existing dashboard contradicts a convention, follow the dashboard and flag the divergence once.
When reviewing dashboards:
- Cite the specific JSON path or panel/variable name and show the fix inline.
- State what's wrong and how to fix it.
Integration
- Data source query languages (
promql,metricsql,logsql) governtargets[].exprcontents. This skill governs the rest of the panel. alertinggoverns alert rules and notification routing; panel-attached alert configuration via the panel editor's Alert tab is the only crossover.provisioninggoverns file-based dashboard delivery (Terraform, Grizzly, Foundation SDK,provisioning/dashboards/*.yaml); this skill governs the JSON those tools emit and consume.
Critical rules
- Never read or rewrite a whole dashboard file. Map with jq, mutate the selected path or splice an extracted panel,
then verify with a semantic diff. Use
(.panels[], .panels[].panels[]?)to cover collapsed-row panels. uidis the durable handle. Externalize all cross-system references (provisioning, links, scripts) viauid, neveridortitle.- Never hand-edit
schemaVersionorversion. Grafana manages both. - Set units explicitly on every numeric field that displays values. No units = no scaling = misleading axes.
- Wrap counters in
rate()/increase()at the query level before transformations. Raw counters throughReduceare meaningless. - Order variables so parents render before children. Visual order = dependency order; no UI indication of dependency.
- Place
${__all_variables}before any explicitvar-<name>=...in data link URLs so explicit values override. - Audit tag-filtered annotation queries before externally sharing a dashboard — they leak across the organization.
- Treat actions as user-visible, unauthenticated — never embed credentials or assume the target API is private.