← Back to DevBytes

Grafana Dashboard Design: Complete Implementation Guide

Grafana Dashboard Design: Complete Implementation Guide

Grafana dashboards are the visual interface that transforms raw metrics, logs, and traces into actionable insights. A well-designed dashboard tells a clear story at a glance, while a poorly designed one creates confusion and slows down incident response. This guide walks through every aspect of dashboard implementation—from the JSON model fundamentals to advanced provisioning, templating, and performance optimization—with complete, production-ready code examples.

Understanding Grafana Dashboard Architecture

A Grafana dashboard is fundamentally a JSON document that defines every visual element, data source connection, and interactive behavior. This JSON model can be created through the Grafana UI, imported from files, or managed programmatically via the API. Understanding the underlying structure gives you complete control over dashboard design.

The dashboard JSON consists of several core sections:

Why Dashboard Design Matters

Effective dashboard design directly impacts mean time to detect (MTTD) and mean time to resolve (MTTR). When an incident occurs, operators need to answer three questions within seconds: What is broken? How severe is it? Where should I look next? A dashboard that buries critical signals under decorative widgets or forces users to scroll through dozens of panels fails at its primary job. Good design reduces cognitive load by applying consistent layout patterns, deliberate color usage, and progressive disclosure—showing high-level status first, with drill-down paths for deeper investigation.

Beyond incident response, well-designed dashboards foster data literacy across teams. When non-technical stakeholders can interpret dashboards without guidance, data becomes a shared language rather than a siloed resource.

Dashboard JSON Model: A Complete Example

Let's build a complete dashboard from scratch. The JSON below defines a monitoring dashboard for a web service, including templated variables, time series panels, stat panels, and threshold-driven alerts. Every field is annotated so you understand exactly what it controls.

{
  "annotations": {
    "list": [
      {
        "builtIn": 1,
        "datasource": {
          "type": "prometheus",
          "uid": "prometheus-uid"
        },
        "enable": true,
        "hide": false,
        "iconColor": "rgba(0, 211, 249, 1)",
        "name": "Deployments",
        "target": {
          "limit": 100,
          "matchAny": false,
          "tags": [],
          "type": "dashboard"
        },
        "type": "dashboard"
      }
    ]
  },
  "editable": true,
  "fiscalYearStartMonth": 0,
  "graphTooltip": 1,
  "id": null,
  "links": [
    {
      "asDropdown": true,
      "icon": "external link",
      "includeVars": true,
      "keepTime": true,
      "tags": ["infrastructure"],
      "targetBlank": true,
      "title": "Related Dashboards",
      "type": "dashboards"
    }
  ],
  "liveNow": false,
  "panels": [
    {
      "datasource": {
        "type": "prometheus",
        "uid": "prometheus-uid"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "percent"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 4,
        "w": 4,
        "x": 0,
        "y": 0
      },
      "id": 1,
      "options": {
        "orientation": "auto",
        "reduceOptions": {
          "calcs": ["lastNotNull"],
          "fields": "",
          "values": false
        },
        "showThresholdLabels": false,
        "showThresholdMarkers": true,
        "text": {}
      },
      "pluginVersion": "10.0.0",
      "title": "CPU Usage %",
      "type": "gauge",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "prometheus-uid"
          },
          "editorMode": "code",
          "expr": "100 - (avg(rate(node_cpu_seconds_total{mode=\"idle\", instance=\"$instance\"}[5m])) * 100)",
          "legendFormat": "__auto",
          "range": true,
          "refId": "A"
        }
      ]
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "prometheus-uid"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "drawStyle": "line",
            "fillOpacity": 25,
            "gradientMode": "opacity",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "lineInterpolation": "linear",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "auto",
            "spanNulls": false,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "bytes"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 8,
        "x": 4,
        "y": 0
      },
      "id": 2,
      "options": {
        "legend": {
          "calcs": ["mean", "max"],
          "displayMode": "table",
          "placement": "bottom",
          "showLegend": true
        },
        "tooltip": {
          "mode": "multi",
          "sort": "desc"
        }
      },
      "pluginVersion": "10.0.0",
      "title": "Memory Usage Over Time",
      "type": "timeseries",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "prometheus-uid"
          },
          "editorMode": "code",
          "expr": "node_memory_MemTotal_bytes{instance=\"$instance\"} - node_memory_MemAvailable_bytes{instance=\"$instance\"}",
          "legendFormat": "Used Memory",
          "range": true,
          "refId": "A"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "prometheus-uid"
          },
          "editorMode": "code",
          "expr": "node_memory_MemTotal_bytes{instance=\"$instance\"}",
          "legendFormat": "Total Memory",
          "range": true,
          "refId": "B"
        }
      ]
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "prometheus-uid"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "custom": {
            "align": "center",
            "cellOptions": {
              "type": "auto"
            },
            "inspect": false
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "orange",
                "value": 70
              },
              {
                "color": "red",
                "value": 90
              }
            ]
          },
          "unit": "percent"
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "Value"
            },
            "properties": [
              {
                "id": "custom.cellOptions",
                "value": {
                  "mode": "gradient",
                  "type": "color-background"
                }
              }
            ]
          }
        ]
      },
      "gridPos": {
        "h": 6,
        "w": 12,
        "x": 0,
        "y": 8
      },
      "id": 3,
      "options": {
        "footer": {
          "countRows": false,
          "enablePagination": false,
          "fields": "",
          "show": false
        },
        "showHeader": true,
        "sortBy": []
      },
      "pluginVersion": "10.0.0",
      "title": "Disk Usage by Mount",
      "type": "table",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "prometheus-uid"
          },
          "editorMode": "code",
          "expr": "(node_filesystem_size_bytes{instance=\"$instance\", mountpoint=~\"$mountpoint\"} - node_filesystem_free_bytes{instance=\"$instance\", mountpoint=~\"$mountpoint\"}) / node_filesystem_size_bytes{instance=\"$instance\", mountpoint=~\"$mountpoint\"} * 100",
          "format": "table",
          "legendFormat": "__auto",
          "range": true,
          "refId": "A"
        }
      ]
    }
  ],
  "refresh": "30s",
  "schemaVersion": 38,
  "style": "dark",
  "tags": ["monitoring", "infrastructure", "prometheus"],
  "templating": {
    "list": [
      {
        "current": {
          "selected": false,
          "text": "node-exporter:9100",
          "value": "node-exporter:9100"
        },
        "datasource": {
          "type": "prometheus",
          "uid": "prometheus-uid"
        },
        "definition": "label_values(node_cpu_seconds_total, instance)",
        "hide": 0,
        "includeAll": false,
        "label": "Instance",
        "multi": false,
        "name": "instance",
        "options": [],
        "query": {
          "query": "label_values(node_cpu_seconds_total, instance)",
          "refId": "PrometheusVariableQueryEditor-VariableQuery"
        },
        "refresh": 1,
        "regex": "",
        "skipUrlSync": false,
        "sort": 1,
        "type": "query"
      },
      {
        "current": {
          "selected": false,
          "text": "/",
          "value": "/"
        },
        "datasource": {
          "type": "prometheus",
          "uid": "prometheus-uid"
        },
        "definition": "label_values(node_filesystem_size_bytes{instance=\"$instance\"}, mountpoint)",
        "hide": 0,
        "includeAll": false,
        "label": "Mountpoint",
        "multi": false,
        "name": "mountpoint",
        "options": [],
        "query": {
          "query": "label_values(node_filesystem_size_bytes{instance=\"$instance\"}, mountpoint)",
          "refId": "PrometheusVariableQueryEditor-VariableQuery"
        },
        "refresh": 2,
        "regex": "",
        "skipUrlSync": false,
        "sort": 1,
        "type": "query"
      }
    ]
  },
  "time": {
    "from": "now-6h",
    "to": "now"
  },
  "timepicker": {
    "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h"]
  },
  "timezone": "browser",
  "title": "Service Infrastructure Overview",
  "uid": "infra-overview-001",
  "version": 1,
  "weekStart": ""
}

This complete dashboard JSON illustrates several critical design patterns: the top row uses compact stat panels for at-a-glance health, the middle row dedicates more vertical space to trend analysis, and the table panel at the bottom provides detailed drill-down data. The templating variables for $instance and $mountpoint make the dashboard reusable across all your infrastructure.

Dashboard Provisioning: Infrastructure as Code

Manual dashboard creation doesn't scale. For production environments, use Grafana's provisioning system to manage dashboards as code. This approach lets you version dashboards in Git, deploy them via CI/CD pipelines, and ensure consistency across environments.

First, configure the provisioning directory in Grafana's configuration:

# grafana.ini or provisioned datasources/dashboards YAML
# Path to provisioning directory
provisioning:
  dashboards:
    path: /etc/grafana/provisioning/dashboards

Create a dashboard provider YAML file that tells Grafana where to find dashboard JSON files and how to handle updates:

# /etc/grafana/provisioning/dashboards/infrastructure.yaml
apiVersion: 1

providers:
  - name: 'infrastructure-dashboards'
    orgId: 1
    folder: 'Infrastructure'
    folderUid: 'infra-folder-uid'
    type: file
    disableDeletion: false
    updateIntervalSeconds: 30
    allowUiUpdates: true
    options:
      path: '/etc/grafana/dashboards/infrastructure'
      foldersFromFilesStructure: true

Now place your dashboard JSON files in the configured path. Grafana will automatically pick them up within the update interval:

# Directory structure
/etc/grafana/dashboards/
└── infrastructure/
    ├── overview.json
    ├── cpu-details.json
    └── memory-details.json

For Kubernetes deployments, bundle dashboards in a ConfigMap and mount them into the Grafana pod:

apiVersion: v1
kind: ConfigMap
metadata:
  name: grafana-dashboards
  namespace: monitoring
data:
  infrastructure-overview.json: |
    {
      "title": "Infrastructure Overview",
      "uid": "infra-overview-001",
      ...full dashboard JSON...
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grafana
spec:
  template:
    spec:
      containers:
        - name: grafana
          volumeMounts:
            - name: dashboards
              mountPath: /etc/grafana/dashboards/infrastructure
      volumes:
        - name: dashboards
          configMap:
            name: grafana-dashboards

Variables and Templating: Making Dashboards Dynamic

Hardcoded queries create brittle dashboards that work for exactly one scenario. Variables transform a single dashboard into a multi-purpose tool that adapts to different data sources, environments, and time ranges. Grafana supports several variable types, each suited to different use cases.

Query variables populate dropdowns from data source results. The example below queries Prometheus for all Kubernetes namespaces, letting users filter the entire dashboard by namespace:

{
  "name": "namespace",
  "label": "Namespace",
  "type": "query",
  "datasource": {
    "type": "prometheus",
    "uid": "prometheus-uid"
  },
  "query": {
    "query": "label_values(kube_namespace_labels, namespace)",
    "refId": "PrometheusVariableQueryEditor-VariableQuery"
  },
  "refresh": 1,
  "includeAll": true,
  "multi": true,
  "allValue": ".*",
  "sort": 1
}

Custom variables define static option lists—perfect for environments, regions, or tiers that don't change dynamically:

{
  "name": "environment",
  "label": "Environment",
  "type": "custom",
  "current": {
    "selected": true,
    "text": "production",
    "value": "production"
  },
  "options": [
    {"text": "Development", "value": "dev", "selected": false},
    {"text": "Staging", "value": "staging", "selected": false},
    {"text": "Production", "value": "production", "selected": true}
  ],
  "multi": false,
  "includeAll": false
}

Chained variables create dependent dropdowns where the second variable's options are filtered by the first variable's selection. This is essential for drilling down from cluster to namespace to pod:

{
  "name": "pod",
  "label": "Pod",
  "type": "query",
  "datasource": {
    "type": "prometheus",
    "uid": "prometheus-uid"
  },
  "query": {
    "query": "label_values(kube_pod_info{namespace=\"$namespace\"}, pod)",
    "refId": "PrometheusVariableQueryEditor-VariableQuery"
  },
  "refresh": 2,
  "includeAll": false,
  "multi": false
}

The refresh field controls when a variable updates: 1 means "on dashboard load," 2 means "on time range change." For chained variables, always set the child variable's refresh to 2 so it re-queries when the parent changes.

Panel Design: Choosing the Right Visualization

Panel selection is the single most impactful design decision. Each panel type communicates a specific category of information optimally—using the wrong type obscures patterns or misleads viewers.

Time Series panels are ideal for trends, patterns, and comparisons over time. Configure them with meaningful legend calculations and multi-mode tooltips:

{
  "type": "timeseries",
  "title": "HTTP Request Rate",
  "fieldConfig": {
    "defaults": {
      "unit": "reqps",
      "custom": {
        "drawStyle": "line",
        "lineInterpolation": "smooth",
        "fillOpacity": 15,
        "stacking": {
          "mode": "normal",
          "group": "A"
        }
      }
    },
    "overrides": [
      {
        "matcher": {"id": "byRegexp", "options": "/5xx/"},
        "properties": [
          {"id": "color", "value": "#E02D1B"},
          {"id": "custom.lineWidth", "value": 2}
        ]
      }
    ]
  },
  "options": {
    "legend": {
      "displayMode": "table",
      "placement": "right",
      "calcs": ["mean", "max", "lastNotNull"]
    },
    "tooltip": {
      "mode": "multi",
      "sort": "desc"
    }
  },
  "targets": [
    {
      "expr": "sum(rate(http_requests_total{service=\"$service\"}[5m])) by (status_code)",
      "legendFormat": "{{status_code}}",
      "refId": "A"
    }
  ]
}

Stat panels display a single prominent value with sparkline context. They're perfect for row-level summaries and SLO compliance indicators:

{
  "type": "stat",
  "title": "Error Rate (7d)",
  "fieldConfig": {
    "defaults": {
      "unit": "percent",
      "thresholds": {
        "mode": "absolute",
        "steps": [
          {"color": "green", "value": null},
          {"color": "orange", "value": 0.1},
          {"color": "red", "value": 1}
        ]
      },
      "mappings": []
    }
  },
  "options": {
    "reduceOptions": {
      "calcs": ["lastNotNull"]
    },
    "graphMode": "area",
    "colorMode": "background",
    "textMode": "auto"
  },
  "targets": [
    {
      "expr": "(sum(rate(http_requests_total{status_code=~\"5..\"}[7d])) / sum(rate(http_requests_total[7d]))) * 100",
      "refId": "A"
    }
  ]
}

Gauge panels show where a value falls within a defined range—ideal for capacity planning and resource saturation:

{
  "type": "gauge",
  "title": "Disk Utilization",
  "fieldConfig": {
    "defaults": {
      "unit": "percent",
      "min": 0,
      "max": 100,
      "thresholds": {
        "mode": "absolute",
        "steps": [
          {"color": "blue", "value": null},
          {"color": "yellow", "value": 60},
          {"color": "orange", "value": 80},
          {"color": "red", "value": 90}
        ]
      }
    }
  },
  "options": {
    "orientation": "horizontal",
    "showThresholdLabels": true,
    "showThresholdMarkers": true
  }
}

Advanced Table Panels with Cell Formatting

Table panels are powerful for log-style data and multi-dimensional breakdowns. Modern Grafana supports cell-level formatting that creates heatmap-like visual cues:

{
  "type": "table",
  "title": "Pod Resource Usage",
  "fieldConfig": {
    "defaults": {
      "custom": {
        "align": "center",
        "cellOptions": {
          "type": "auto"
        },
        "width": 150
      }
    },
    "overrides": [
      {
        "matcher": {"id": "byName", "options": "CPU %"},
        "properties": [
          {"id": "unit", "value": "percent"},
          {"id": "custom.cellOptions", "value": {
            "mode": "gradient",
            "type": "color-background",
            "colors": [
              {"color": "green", "value": 0},
              {"color": "yellow", "value": 50},
              {"color": "red", "value": 90}
            ]
          }},
          {"id": "thresholds", "value": {
            "mode": "absolute",
            "steps": [
              {"color": "green", "value": null},
              {"color": "red", "value": 80}
            ]
          }}
        ]
      },
      {
        "matcher": {"id": "byName", "options": "Memory %"},
        "properties": [
          {"id": "unit", "value": "percent"},
          {"id": "custom.cellOptions", "value": {
            "mode": "gradient",
            "type": "color-background"
          }}
        ]
      }
    ]
  },
  "options": {
    "showHeader": true,
    "footer": {
      "show": true,
      "countRows": true
    }
  },
  "targets": [
    {
      "expr": "sum(container_cpu_usage_seconds_total{namespace=\"$namespace\"}) by (pod) / sum(kube_pod_container_resource_limits{resource=\"cpu\", namespace=\"$namespace\"}) by (pod) * 100",
      "format": "table",
      "refId": "A"
    }
  ]
}

Layout Best Practices: The Information Hierarchy

Dashboard layout should follow a deliberate information hierarchy that guides the viewer's eye from summary to detail. Research on visual scanning patterns shows that users process dashboards in an F-shaped pattern—top row first, then scanning downward along the left edge.

Row 1 (Summary Row): Place 4–6 stat or gauge panels spanning the full width. These should answer "Is everything healthy?" with green/red color coding visible from across the room. Keep panels small (2–4 grid units wide, 3–4 units tall).

Row 2 (Trends): Dedicate this row to time series panels showing 1-hour to 24-hour trends. Use 8–12 grid units width per panel. This row answers "What's been happening recently?" and helps correlate incidents with metric changes.

Row 3+ (Details): Table panels, log panels, or detailed breakdowns go here. These rows answer "Where exactly is the problem?" and are primarily used during active investigation.

Here's a grid layout reference showing the coordinate system:

// Grid coordinates reference
// The dashboard uses a 24-column grid system
// Each panel's gridPos specifies:
//   x: horizontal position (0-23)
//   y: vertical position (in rows)
//   w: width in grid units (1-24)
//   h: height in grid units

// Example layout for a 4-panel summary row:
// Panel 1: {x: 0,  y: 0, w: 6, h: 4}  // CPU Gauge
// Panel 2: {x: 6,  y: 0, w: 6, h: 4}  // Memory Gauge
// Panel 3: {x: 12, y: 0, w: 6, h: 4}  // Disk Gauge
// Panel 4: {x: 18, y: 0, w: 6, h: 4}  // Network Gauge

// Trend row starting at y=4:
// Panel 5: {x: 0,  y: 4, w: 12, h: 8} // CPU Trends
// Panel 6: {x: 12, y: 4, w: 12, h: 8} // Memory Trends

// Detail row starting at y=12:
// Panel 7: {x: 0, y: 12, w: 24, h: 8} // Full-width table

Color and Threshold Design

Color is the fastest channel for conveying meaning, but misuse creates confusion. Follow these rules for professional dashboard color usage:

Threshold configuration example aligned with SLO targets:

{
  "fieldConfig": {
    "defaults": {
      "thresholds": {
        "mode": "absolute",
        "steps": [
          {
            "color": "green",
            "value": null
          },
          {
            "color": "#FFA500",
            "value": 200
          },
          {
            "color": "red",
            "value": 500
          }
        ]
      },
      "unit": "ms"
    }
  }
}

Performance Optimization for Large Dashboards

Dashboards with 50+ panels querying Prometheus simultaneously can overwhelm data sources and slow rendering. Apply these optimization techniques:

Query batching and minification: Combine related queries into single expressions where possible. Instead of four separate queries for CPU user, system, iowait, and idle, use one query with a grouping vector:

// Before: 4 separate queries
// Query 1: node_cpu_seconds_total{mode="user"}
// Query 2: node_cpu_seconds_total{mode="system"}
// Query 3: node_cpu_seconds_total{mode="iowait"}
// Query 4: node_cpu_seconds_total{mode="idle"}

// After: 1 query with label grouping
rate(node_cpu_seconds_total{instance="$instance"}[5m]) 
  * on(instance) group_left 
  count(node_cpu_seconds_total{instance="$instance"}) by (instance)

Panel refresh differentiation: Not every panel needs 30-second refresh. Stat panels showing daily SLO compliance can refresh every hour. Set per-panel query intervals in the data source configuration or use dashboard variables to control refresh behavior.

Lazy-load rows: For large dashboards, collapse detail rows by default so their queries don't execute until the user expands them. Configure row models with "collapse": true:

{
  "collapsed": true,
  "title": "Database Details",
  "type": "row",
  "panels": [
    ...panels inside this row...
  ],
  "gridPos": {
    "h": 1,
    "w": 24,
    "x": 0,
    "y": 20
  }
}

Pre-aggregated recording rules: For metrics queried by many dashboards, create Prometheus recording rules that pre-compute expensive aggregations. Query the recorded metric instead of raw data:

# Prometheus recording rule
groups:
  - name: dashboard_optimizations
    rules:
      - record: job:http_requests_total:rate5m
        expr: sum(rate(http_requests_total[5m])) by (job, status_code)
      - record: instance:node_cpu:utilization5m
        expr: 100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance) * 100)

# Dashboard query then uses the pre-computed metric:
# job:http_requests_total:rate5m{job="$job"}
# This executes in milliseconds instead of computing across raw data

Annotations: Marking Events on the Timeline

Annotations overlay deployment markers, incidents, or configuration changes directly onto time series graphs. They transform raw metrics into a correlated narrative. Configure both built-in and query-based annotations:

{
  "annotations": {
    "list": [
      {
        "builtIn": 1,
        "datasource": {
          "type": "prometheus",
          "uid": "prometheus-uid"
        },
        "enable": true,
        "hide": false,
        "iconColor": "rgba(255, 96, 96, 1)",
        "name": "Kubernetes Events",
        "target": {
          "limit": 100,
          "matchAny": false,
          "tags": ["kubernetes", "events"],
          "type": "dashboard"
        },
        "type": "dashboard"
      },
      {
        "datasource": {
          "type": "prometheus",
          "uid": "prometheus-uid"
        },
        "enable": true,
        "hide": false,
        "iconColor": "rgba(0, 211, 249, 1)",
        "name": "Deployments",
        "target": {
          "expr": "kube_deployment_status_observed_generation{namespace=\"$namespace\"} unless kube_deployment_status_observed_generation{namespace=\"$namespace\"} offset 1m",
          "limit": 100,
          "tags": [],
          "type": "query"
        },
        "type": "query"
      }
    ]
  }
}

The query-based deployment annotation detects when a deployment's observed generation changes, marking each deployment event as a vertical line on every graph. Operators can instantly correlate latency spikes with deployment timestamps.

Dashboard Links and Navigation Flow

Well-connected dashboards reduce the time operators spend hunting for related information. Configure drill-down links that preserve time range and variable context:

{
  "links": [
    {
      "asDropdown": false,
      "icon": "dashboard",
      "includeVars": true,
      "keepTime": true,
      "tags": ["infrastructure", "detail"],
      "targetBlank": false,
      "title": "Infrastructure Details",
      "type": "dashboards"
    },
    {
      "asDropdown": false,
      "icon": "external link",
      "includeVars": true,
      "keepTime": true,
      "tags": [],
      "targetBlank": true,
      "title": "Runbook Documentation",
      "type": "link",
      "url": "https://wiki.example.com/runbooks/infrastructure"
    }
  ]
}

For panel-level drill-down, configure data link templates that inject variable values into URLs:

{
  "fieldConfig": {
    "defaults": {
      "links": [

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles