grafana — Cursor rule: dashboards as code
A rule that sets one style for Grafana dashboards, alerts and data sources as code: structure, visual standards, query performance.
.cursor/rules/grafana.mdcWhat grafana does
The rule treats Grafana dashboards, alerts and data sources as code artifacts. Everything lives in JSON or YAML, sits in Git and gets provisioned through the Grafana API. Manual UI edits are fine only for early prototyping, because you lose version history and review.
For complex or generated dashboards the rule prefers the Foundation SDKs for Go, Python and TypeScript over hand-written JSON. Templated variables are mandatory — without them you end up with duplicate prod and dev dashboards and hardcoded instance names. Prometheus counters go through rate() or irate(), never sum(), otherwise the panel shows a cumulative total instead of a rate.
The visual rules are strict: Y-axis always zero-based, thin lines, at most 4 metrics per panel. Auto-refresh should sit at 5 or 15 minutes; anything under a minute belongs on alerting dashboards. Timing metrics need care too — mean and median hide outliers, so the rule pushes max, p99 and p75 instead.
What it helps with
- move Grafana dashboards into Git files instead of manual UI edits
- kill duplicate prod and dev dashboards with templated variables
- fix panels where a counter uses
sum()and climbs forever - make panels consistent: zero-based Y-axis, no more than 4 lines
- cut load on the metric database with a sane auto-refresh interval
Example prompts for the agent
- Build a service dashboard following the grafana rule: instance and environment variables, zero-based Y-axis
- Review my Prometheus queries — counters should use rate(), not sum()
- Write the alerts and data sources as YAML so I can provision them through the Grafana API
How to install grafana
Manually: put the files in place
.cursor/rules/grafana.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/grafana.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/grafana.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/grafana.mdManually: put the files in place
.clinerules/grafana.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about grafana
How do I install the grafana rule in Cursor?
Create .cursor/rules/grafana.mdc in your project root and paste the rule contents into it. Cursor picks it up automatically.
What does the grafana rule do?
It sets standards for Grafana dashboards, alerts and data sources as code: declarative JSON/YAML in Git, templated variables, visual requirements for panels and query optimization.
How does the rule help with Prometheus?
It requires counters to use rate() or irate(), and timing metrics to use max, p99 or p75 rather than mean and median, which hide outliers.
Source text
grafana.mdc · CC0-1.0
---
description: This guide defines definitive best practices for developing, structuring, and maintaining Grafana dashboards, alerts, and data sources as code, ensuring readability, performance, and scalability across teams.
globs: **/*
---
# Grafana Best Practices
Our team treats Grafana dashboards, alerts, and data sources as first-class code artifacts. This means applying software engineering principles: version control, peer review, automated testing, and CI/CD. Adhere strictly to these guidelines to ensure our observability platform remains robust, maintainable, and actionable.
## 1. Code Organization and Structure
Define all Grafana resources declaratively using Observability as Code (OaC). This enables Git-based workflows, automated provisioning, and consistent deployments.
### 1.1. Declarative Configuration (OaC)
**Always** define dashboards, alerts, and data sources in JSON/YAML files. Store these in Git and provision them via Grafana's API. Avoid manual UI configuration for anything beyond initial prototyping.
❌ **BAD**: Manual UI changes, no version history.
✅ **GOOD**: Git-versioned JSON/YAML files, automated provisioning.
```json
// my-service-dashboard.json
{
"apiVersion": 1,
"title": "My Service Overview",
"uid": "my-service-overview",
"panels": [
// ... panel definitions ...
],
"templating": {
"list": [
// ... variables ...
]
}
}
```
### 1.2. Utilize Foundation SDKs
For complex or dynamically generated dashboards, **prefer** using Grafana Foundation SDKs (Go, Python, TypeScript) to programmatically define resources. This provides strong typing, validation, and better code reusability than raw JSON manipulation.
```typescript
// dashboard.ts (using TypeScript SDK)
import { Dashboard, Row, Panel, GraphPanel } from '@grafana/sdk';
const dashboard = new Dashboard({
title: 'Service Health',
uid: 'service-health',
panels: [
new Row({
panels: [
new GraphPanel({
title: 'CPU Utilization',
targets: [{ expr: 'sum(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance)' }],
yAxis: { min: 0 }
})
]
})
]
});
console.log(JSON.stringify(dashboard.toGrafanaJson(), null, 2));
```
### 1.3. Templated Variables
**Mandate** the use of templated variables for dynamic filtering and reusability across environments or instances. This prevents dashboard duplication.
❌ **BAD**: Hardcoded instance names or environment-specific queries.
```json
// Duplicated dashboard for 'prod' and 'dev'
{ "title": "Prod CPU Usage", "targets": [{ "expr": "node_cpu_seconds_total{instance='prod-app-01'}" }] }
{ "title": "Dev CPU Usage", "targets": [{ "expr": "node_cpu_seconds_total{instance='dev-app-01'}" }] }
```
✅ **GOOD**: Single dashboard with variables.
```json
{
"title": "Service CPU Usage",
"templating": {
"list": [
{ "name": "instance", "type": "query", "query": "label_values(node_cpu_seconds_total, instance)" },
{ "name": "environment", "type": "custom", "options": ["prod", "dev"], "current": { "value": "prod" } }
]
},
"panels": [
{
"title": "CPU Utilization on $instance ($environment)",
"targets": [{ "expr": "node_cpu_seconds_total{instance=\"$instance\", env=\"$environment\"}" }]
}
]
}
```
## 2. Common Patterns and Anti-patterns
Design dashboards for immediate diagnostic value and clarity.
### 2.1. Diagnostic Frameworks (USE & Four Golden Signals)
**Structure dashboards** around the USE method (Utilization, Saturation, Errors) for resource-centric views and the Four Golden Signals (Rate, Errors, Duration, Latency) for service-centric views. Each row should focus on a single resource or signal.
* **USE Method**: Left column for Utilization, right for Saturation/Errors.
* **Four Golden Signals**: Panels ordered logically (e.g., Rate, Errors, Latency).
### 2.2. Visual Consistency
**Enforce** these visual standards for immediate comprehension:
* **Y-axis**: **Always** zero-based.
* **Graph Type**: Prefer thin line graphs (`fill: 0`) for time-series. Use bar charts for quantities per interval (e.g., rates, CPU usage).
* **Metrics per Panel**: Limit to **max 4 lines** per graph panel. More than this indicates the panel is trying to answer too many questions.
* **Titles & Legends**: Clear, concise panel titles. Informative legends using `alias` to describe metric transformations.
❌ **BAD**: Misleading Y-axis, cluttered panels.
```json
{
"title": "Service Latency",
"type": "graph",
"yAxis": { "min": null }, // Y-axis auto-scales, can hide small changes
"targets": [
{ "expr": "service_latency_p99", "legendFormat": "P99" },
{ "expr": "service_latency_p95", "legendFormat": "P95" },
{ "expr": "service_latency_p75", "legendFormat": "P75" },
{ "expr": "service_latency_mean", "legendFormat": "Mean" },
{ "expr": "service_latency_median", "legendFormat": "Median" } // Too many lines
]
}
```
✅ **GOOD**: Zero-based Y-axis, focused metrics, clear aliases.
```json
{
"title": "Service Latency (P99 & P95)",
"type": "graph",
"yAxis": { "min": 0 }, // Always zero-based
"options": { "legend": { "displayMode": "table", "calcs": ["mean", "max"] } },
"fieldConfig": { "defaults": { "custom": { "drawStyle": "line", "lineInterpolation": "linear", "fillOpacity": 0 } } },
"targets": [
{ "expr": "histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))", "legendFormat": "P99 Latency" },
{ "expr": "histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))", "legendFormat": "P95 Latency" }
]
}
```
### 2.3. Metric Selection
* **Counters**: **Always** use `.rate()` for StatsD counters from Graphite or `rate()`/`irate()` for Prometheus counters. Never `count()` or `sum()`.
* **Timing**: **Prioritize** `max`, `p99`, or `p75` for timing metrics. Avoid `mean` or `median` as they hide outliers.
❌ **BAD**: Using `sum` for a counter.
```json
{ "targets": [{ "expr": "sum(http_requests_total)" }] } // Shows cumulative total, not rate
```
✅ **GOOD**: Using `rate` for a counter.
```json
{ "targets": [{ "expr": "sum(rate(http_requests_total[5m]))" }] } // Shows requests per second
```
## 3. Performance Considerations
Efficient dashboards minimize load on data sources and Grafana itself.
### 3.1. Query Optimization
**Keep queries minimal and efficient**. Avoid excessive aggregations or long time ranges in individual panels unless explicitly needed for historical context. Use `group by` sparingly.
### 3.2. Auto-Refresh Interval
**Set auto-refresh to sensible defaults** (e.g., 5min or 15min). Avoid smaller intervals (<1min) for non-alerting dashboards to prevent unnecessary load on the metric database. For immediate notification, use alerts.
```json
{
"refresh": "5m", // Recommended default
"time": { "from": "now-6h", "to": "now" }
}
```
## 4. Common Pitfalls and Gotchas
Avoid these common mistakes that lead to confusing or inefficient dashboards.
### 4.1. Misleading Y-axis
**Never** allow the Y-axis to auto-scale or start above zero for percentage or count metrics. This distorts trends and makes small changes appear significant.
### 4.2. Overloaded Panels
A panel with too many metrics or complex queries becomes unreadable and slow. If you can't interpret a panel at a glance, it's overloaded. Break it down.
### 4.3. Lack of Context
Every dashboard **must** include a "Legend" text panel at the top. This panel should describe:
* The dashboard's purpose.
* The data flow and any significant transformations.
* Links to relevant documentation or source repositories.
```json
{
"title": "Legend",
"type": "text",
"gridPos": { "x": 0, "y": 0, "w": 24, "h": 2 },
"options": {
"content": "This dashboard monitors the **Payment Service** health. Metrics are collected via Prometheus exporters, aggregated every 15s. [Service Docs](https://internal.wiki/payment-service) | [Source Code](https://github.com/org/payment-service)",
"mode": "markdown"
}
}
```
### 4.4. Inconsistent Annotations
**Standardize annotations** for deployments and critical events. Configure dashboards to query common tags (e.g., `deploy`, `incident`).
```json
{
"annotations": {
"list": [
{
"name": "Deployments",
"datasource": "Grafana",
"iconColor": "rgba(255, 96, 96, 1)",
"enable": true,
"query": { "tags": "deploy", "matchAny": true }
}
]
}
}
```
## 5. Testing Approaches
Treat Grafana configurations as production code, subject to automated testing and review.
### 5.1. Dashboard Linter Integration
**Integrate `grafana/dashboard-linter`** into your CI/CD pipeline. This tool automatically scans JSON definitions for common anti-patterns (e.g., missing titles, non-zero-based axes, excessive series).
```bash
# Example CI/CD step
- name: Lint Grafana Dashboards
run: |
go install github.com/grafana/dashboard-linter@latest
dashboard-linter lint dashboards/*.json
```
### 5.2. Peer Review
**Require peer review** for all changes to Grafana configuration files (dashboards, alerts, data sources). This ensures adherence to standards and catches logical errors before deployment.
### 5.3. Automated Provisioning & Git Sync
**Leverage Grafana's provisioning API** for automated deployment. For Grafana 12+, **explore and adopt Git Sync** to manage dashboards directly from the UI with Git workflows (branches, PRs). This bridges the gap between UI-driven iteration and code-driven version control.
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc