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]
- Build the plugin (usually
npm run buildoryarn builddepending on toolkit) - Copy the plugin directory into Grafana’s
pluginsdirectory (for example:/var/lib/grafana/plugins/my-custom-panel) - Restart Grafana:
systemctl restart grafana-server(or the appropriate command for your environment) - 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.