Everything the UI does ends up in this JSON; reading it is the step from clicking dashboards to reviewing, generating and versioning them.
Every dashboard is one JSON document, visible under Edit, Settings, JSON Model. The important fields:
uid: the stable identifier used in URLs (/d/<uid>/<slug>) and links.idis a per-instance database number; it is ignored on import and should benullin files.title,tags,timezone,time(default range),refresh,graphTooltip.templating.list: the variables, each withquery,datasource,multi,includeAll,current.annotations.listandlinks.panels[]: each withtype,gridPos,datasource({"type": "prometheus", "uid": "prom-main"}),targets(queries),fieldConfig.defaultsandoverrides,options, andtransformations. Rows are panels of typerowholding collapsed panels.schemaVersionandversion: Grafana migrates old schemas on load; the version number increases with every save.
Export (Share, Export) offers the raw model or Export for sharing externally, which replaces concrete data source UIDs with placeholders like ${DS_PROMETHEUS} listed under __inputs. On Import (Dashboards, New, Import) the user maps each input to a local data source, and that is how dashboards from grafana.com/dashboards, where every published dashboard has a numeric id, work across installations.
Versions (Settings, Versions) lists every save with author and message, lets you compare two versions as a JSON diff, and restore an older one, which creates a new version rather than rewriting history. Provisioned dashboards keep their history too but are overwritten from the file.
Grafana 12 adds a v2 schema and a Kubernetes-style dashboards API (dashboard.grafana.app), used by the new layouts and Git Sync; the classic model above remains supported and is still what most tooling emits.
Example
{
"uid": "api-overview",
"id": null,
"title": "API overview",
"tags": ["api", "service"],
"timezone": "browser",
"schemaVersion": 41,
"version": 17,
"time": {"from": "now-6h", "to": "now"},
"refresh": "1m",
"graphTooltip": 1,
"templating": {"list": [{
"name": "job", "type": "query", "query": "label_values(up, job)",
"datasource": {"type": "prometheus", "uid": "${DS_PROMETHEUS}"},
"refresh": 2, "multi": false, "includeAll": false}]},
"__inputs": [{"name": "DS_PROMETHEUS", "type": "datasource",
"pluginId": "prometheus", "label": "Prometheus"}],
"panels": [{
"id": 1, "type": "timeseries", "title": "Requests",
"gridPos": {"x": 0, "y": 0, "w": 12, "h": 8},
"datasource": {"type": "prometheus", "uid": "${DS_PROMETHEUS}"},
"targets": [{"refId": "A", "expr": "sum(rate(http_requests_total{job=\"$job\"}[$__rate_interval]))"}],
"fieldConfig": {"defaults": {"unit": "reqps"}, "overrides": []},
"options": {}
}]
}Remember
- uid is stable and used in URLs; id is instance-local and should be null in files
- Export for sharing externally replaces data sources with ${DS_*} inputs mapped on import
- Versions can be compared as a JSON diff and restored
Watch out for
- Copying a dashboard JSON between instances with concrete data source uids that don't exist there, so every panel shows "datasource not found".