ProjectOperations CLI · 2026
Grafana CLI
Command-line access to Grafana dashboards, data sources, alerts, folders and annotations. Scripted dashboard management and alert triage without the web UI.
- Go
- Grafana API
- JSON
grafana dashboard list --tag monitoring -o json[{"uid":"abc123","title":"API latency","folder":"Core"}…] grafana alert rule list --folder abc1232 rules API 5xx (firing) Queue depth (normal) grafana annotation list --tags deploy --limit 51705312800000 Deployed v1.2.3 tags=[deploy]Operations CLI · Project
Grafana CLI is how I work with Grafana without the browser: dashboards, data sources, folders, alert rules and annotations from the terminal. It is for operators doing inventory and migration work, and for coding agents that cannot click. Before it, the same tasks meant a curl with a token header and a URL I retyped every time.
A dashboard is already JSON, but the web UI is usually the only place anyone edits it. That makes the boring questions expensive to answer. Which dashboards point at a data source I am about to delete. Which alert rules live in this folder, and what was annotated during last night’s deploy. Each is one request, and none of them is worth a browser session.
Resource then action
Every command reads as resource then action, and cli-go/cmd/ mirrors that shape with one package per resource. dashboard/list.go and alert/rule/create.go sit exactly where the command name says they should.
| Area | Command groups |
|---|---|
| Dashboards and data | dashboard, folder, datasource, library-element, correlation |
| Alerting and events | alert rule, contact-point, policy, silence, mute-timing, annotation |
| Access | org, user, team, service-account and its tokens |
| Administration | admin, snapshot, playlist, preferences, config, update |
Lists and gets take -o table, -o json or -o yaml, and grafana config set defaults.output json makes JSON the standing default. Create and update take -f with JSON or YAML, where -f - reads stdin.
Config and transport
grafana login asks for the URL, the auth method, the credential and a profile name. It tests the connection before writing ~/.config/grafana-cli/config.yaml. Settings resolve in one order: flags, then environment variables, then the profile, then defaults. GRAFANA_URL and GRAFANA_TOKEN are enough for CI, with GRAFANA_ORG_ID to pin the organization. A shared Factory in internal/cmdutil/factory.go hands every command the same config, client and IO streams, so the global flags behave identically everywhere. --verbose wraps the HTTP transport and logs each request to stderr, with Authorization, Cookie and X-Api-Key replaced by [REDACTED] while rate limit headers stay readable. Lists page at one hundred results by default.
Choices and their cost
- Service account tokens are the documented path, not a personal login. A token is not tied to a leaver and can carry only the role a workflow needs. The cost is one setup step before the first command does anything.
- Safety is two independent switches.
--read-onlyblocks every command annotated as mutating, while--no-inputonly removes prompts. So--no-inputnever approves a delete, which still needs its own--confirm. - Nothing normalises the dashboard model.
dashboard exportreturns Grafana JSON andimporttakes the same JSON back. A dashboard written by a newer Grafana keeps working, and a diff between two instances stays a diff of the real thing. datasource querygoes through the Grafana proxy rather than straight to Prometheus or Loki. One token then covers every backend. The trade-off is inherited limits, since a data source Grafana cannot reach is not reachable here either. I would revisit this if the CLI ever needed to run where Grafana does not.
An inventory pass
export GRAFANA_URL=https://grafana.example.com
export GRAFANA_TOKEN=glsa_xxxxxxxxxxxx
grafana user current -o json
grafana datasource list --type prometheus -o json | jq -r '.[].uid'
grafana dashboard list --tag monitoring -o json | jq -r '.[].uid'
grafana alert rule list --folder abc123 -o json
grafana annotation list --tags deploy --limit 20
grafana datasource query PROM_UID --expr 'rate(http_requests_total[5m])' --last 1h -o json
That is the pass I run before retiring a data source. Confirm the connection, list the candidates, find which dashboards and rules depend on them, then check what the deploy annotations say about the window. The last line queries through Grafana itself, so no separate Prometheus endpoint or credential is involved.
Where it stops
Coverage follows the Grafana HTTP API, so anything the API does not expose is not here either. There is no drift detection and no reconciliation loop: the CLI applies what you hand it and reports what it found. Destructive commands prompt unless --confirm is passed, and --read-only overrides confirmation entirely. Building from source needs Go 1.26 or later. It is MIT licensed, ships as one binary for macOS, Linux and Windows on amd64 and arm64, and grafana update replaces itself in place. It is also one of the tools the Reckon workspace drives.
Next projectOperations CLI