Custom visualizations with Grafana SDK

For many DevOps engineers and SREs, Grafana’s built-in panels cover 80% of observability use cases. The remaining 20% is where Custom visualizations with Grafana SDK shine: you can turn domain-specific metrics, events, and relationships into tailored UI components…

Custom visualizations with Grafana SDK

For many DevOps engineers and SREs, Grafana’s built-in panels cover 80% of observability use cases. The remaining 20% is where Custom visualizations with Grafana SDK shine: you can turn domain-specific metrics, events, and relationships into tailored UI components that match your mental model, not just your data source.

This post walks through the modern Grafana plugin stack for building custom visualizations with Grafana SDK, with practical examples and code you can adapt for your own observability dashboards.

Why Custom visualizations with Grafana SDK matter for DevOps & SREs

Grafana offers a rich set of built-in visualizations (time series, stat, logs, node graph, Canvas, etc.).[6] But complex production systems often demand:

  • Topology-aware visualizations (service graphs, dependency maps)
  • Custom SLO / error-budget widgets
  • Incident timelines and change overlays
  • Business-specific KPIs blending metrics, logs, and traces

Custom visualizations with Grafana SDK let you:

  • Ship reusable plugins across teams and orgs
  • Enforce consistent visual language for reliability and SLOs
  • Integrate with internal APIs, runbooks, or ticketing systems directly from dashboards

Understanding the Grafana plugin architecture

Custom panels are delivered as Grafana plugins, which the core platform loads dynamically.[1][9] At a high level, a panel plugin consists of:

  • plugin.json – metadata and plugin manifest
  • Panel component – a React/TypeScript component using the Grafana SDK (e.g., @grafana/ui, @grafana/data)
  • Options editor – UI to configure your panel (thresholds, query options, display preferences)
  • Build tooling – usually based on Grafana’s recommended plugin toolkit

The plugin is dropped into the Grafana plugins directory and then becomes selectable as a panel type when building dashboards.[1]

Setting up a development environment

Assuming you already have a running Grafana instance[1][10], you’ll need a local plugin development setup.

1. Create a new panel plugin skeleton

Grafana provides a plugin toolkit to scaffold and build plugins. A typical directory structure for a custom panel might look like:

my-custom-panel/
  src/
    module.tsx
    MyPanel.tsx
    MyPanelEditor.tsx
  plugin.json
  package.json
  tsconfig.json
  webpack.config.js (or Vite config, depending on toolkit)

2. Basic plugin manifest: plugin.json

Here is a minimal plugin.json that registers a custom panel visualization:[1][9]

{
  "type": "panel",
  "name": "My Custom Panel",
  "id": "my-org-my-custom-panel",
  "info": {
    "description": "A simple custom panel built with Grafana SDK",
    "author": {
      "name": "Your Name or Org"
    },
    "keywords": ["grafana", "panel", "custom visualizations"],
    "version": "1.0.0"
  }
}

The id must be globally unique (use your org name or domain to avoid conflicts).

Building a simple custom panel with Grafana SDK

Now let’s build a minimal but useful panel that:

  • Accepts a time series of error rates
  • Calculates the current error budget burn rate
  • Displays a status and burn rate in a compact card

1. Wiring up the panel entrypoint

In older tutorials you may see CommonJS-based examples such as:[1]

const { PanelPlugin } = require('@grafana/data');

const MyPanel = () => {
  return <div>My Custom Panel</div>;
};

export const plugin = new PanelPlugin(MyPanel);

In a modern TypeScript-based plugin, you typically use ES modules and define typed options:

// src/module.tsx
import { PanelPlugin } from '@grafana/data';
import { MyPanel } from './MyPanel';

export interface MyPanelOptions {
  sloTarget: number;
  warningBurnRate: number;
  criticalBurnRate: number;
}

export const plugin = new PanelPlugin<MyPanelOptions>(MyPanel).setPanelOptions((builder) => {
  return builder
    .addNumberInput({
      path: 'sloTarget',
      name: 'SLO target (%)',
      defaultValue: 99.9,
    })
    .addNumberInput({
      path: 'warningBurnRate',
      name: 'Warning burn rate',
      defaultValue: 2,
    })
    .addNumberInput({
      path: 'criticalBurnRate',
      name: 'Critical burn rate',
      defaultValue: 5,
    });
});

This uses the Grafana SDK’s PanelPlugin API and panel options builder to create an options editor UI.

2. Implementing the panel React component

The panel component receives data, time range, and options from Grafana. You read the processed data via PanelProps from @grafana/data.

// src/MyPanel.tsx
import React from 'react';
import { PanelProps } from '@grafana/data';
import { Card, Badge } from '@grafana/ui';
import { MyPanelOptions } from './module';

interface Props extends PanelProps<MyPanelOptions> {}

export const MyPanel: React.FC<Props> = ({ data, options, width, height }) => {
  const series = data.series;
  const lastPoint = series?.fields[1]?.values?.get(series.fields[1].values.length - 1) as number | undefined;

  const sloTarget = options.sloTarget || 99.9;
  const errorRate = lastPoint ?? 0;
  const burnRate = errorRate / (100 - sloTarget);

  let status: 'OK' | 'Warning' | 'Critical' = 'OK';
  if (burnRate >= options.criticalBurnRate) {
    status = 'Critical';
  } else if (burnRate >= options.warningBurnRate) {
    status = 'Warning';
  }

  return (
    <Card style={{ width, height, padding: '8px' }}>
      <Card.Heading>Error Budget Burn Rate</Card.Heading>
      <Card.Description>
        Current error rate: {errorRate.toFixed(3)}%
      </Card.Description>
      <Card.Description>
        Burn rate: {burnRate.toFixed(2)}x
      </Card.Description>
      <Badge color={status === 'Critical' ? 'red' : status === 'Warning' ? 'orange' : 'green'}>
        {status}
      </Badge>
    </Card>
  );
};

This is a concrete example of Custom visualizations with Grafana SDK turning raw metrics into an SLO-aware visual that your on-call engineers can act on.

Deploying your custom visualization

Once your plugin builds successfully, you need to deploy it into Grafana.[1]

  1. Build the plugin (usually npm run build or yarn build depending on toolkit)
  2. Copy the plugin directory into Grafana’s plugins directory (for example: /var/lib/grafana/plugins/my-custom-panel)
  3. Restart Grafana: systemctl restart grafana-server (or the appropriate command for your environment)
  4. Open Grafana, create or edit a dashboard, click Add visualization, and choose your new panel type from the list[10]

Your Custom visualizations with Grafana SDK now behave like any built-in panel: you can configure queries, adjust panel options, and reuse them across dashboards.