Documentation Index
Fetch the complete documentation index at: https://mintlify.com/grafana/grafana/llms.txt
Use this file to discover all available pages before exploring further.
Plugin architecture
Grafana’s plugin system enables extending Grafana with new visualizations, data sources, and applications without modifying core code.
Plugin types
Grafana supports three main plugin types:
Panel plugins
Visualization types that display data:
- Graph, table, stat, gauge, etc.
- Render query results
- Custom visualization logic
- Editor UI for configuration
Data source plugins
Connect Grafana to data sources:
- Query editors
- Backend query execution
- Authentication and connection management
- Alerting support (optional)
App plugins
Full applications within Grafana:
- Custom pages and navigation
- Can include multiple panels and data sources
- Configuration pages
- Custom workflows
Plugin structure
A typical plugin directory:
my-plugin/
├── src/
│ ├── module.ts # Plugin entry point
│ ├── plugin.json # Plugin metadata
│ ├── components/ # React components
│ ├── types.ts # TypeScript types
│ └── img/ # Images and icons
├── pkg/ # Backend (Go) - optional
│ ├── main.go
│ └── plugin/
├── Magefile.go # Build configuration
└── package.json
The plugin.json file defines plugin metadata:
{
"type": "panel",
"name": "My Visualization",
"id": "myorg-mypanel-panel",
"info": {
"description": "Custom visualization plugin",
"author": {
"name": "My Organization"
},
"version": "1.0.0"
},
"dependencies": {
"grafanaDependency": ">=9.0.0",
"plugins": []
},
"backend": true,
"executable": "gpx_mypanel",
"routes": [
{
"path": "api/resource",
"method": "GET",
"url": "http://localhost:8080"
}
]
}
Key fields:
type - Plugin type: panel, datasource, or app
id - Unique plugin identifier (format: org-name-type)
backend - Whether plugin has a backend component
executable - Backend binary name
routes - Custom API routes
dependencies - Required Grafana version and other plugins
Frontend plugin architecture
Panel plugin example
// src/module.ts
import { PanelPlugin } from '@grafana/data';
import { MyPanel } from './components/MyPanel';
import { MyOptions } from './types';
export const plugin = new PanelPlugin<MyOptions>(MyPanel)
.setPanelOptions((builder) => {
return builder
.addTextInput({
path: 'title',
name: 'Title',
description: 'Panel title',
defaultValue: 'My Panel',
})
.addNumberInput({
path: 'fontSize',
name: 'Font size',
defaultValue: 14,
});
});
// src/components/MyPanel.tsx
import React from 'react';
import { PanelProps } from '@grafana/data';
import { MyOptions } from '../types';
interface Props extends PanelProps<MyOptions> {}
export function MyPanel({ options, data, width, height }: Props) {
return (
<div style={{ width, height }}>
<h2>{options.title}</h2>
<div>Data series: {data.series.length}</div>
</div>
);
}
Data source plugin example
// src/module.ts
import { DataSourcePlugin } from '@grafana/data';
import { DataSource } from './datasource';
import { ConfigEditor } from './components/ConfigEditor';
import { QueryEditor } from './components/QueryEditor';
import { MyQuery, MyDataSourceOptions } from './types';
export const plugin = new DataSourcePlugin<DataSource, MyQuery, MyDataSourceOptions>(DataSource)
.setConfigEditor(ConfigEditor)
.setQueryEditor(QueryEditor);
// src/datasource.ts
import { DataSourceInstanceSettings, DataQueryRequest, DataQueryResponse } from '@grafana/data';
import { DataSourceWithBackend } from '@grafana/runtime';
import { MyQuery, MyDataSourceOptions } from './types';
export class DataSource extends DataSourceWithBackend<MyQuery, MyDataSourceOptions> {
constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>) {
super(instanceSettings);
}
// Frontend can override query method for client-side logic
// Otherwise queries go to backend automatically
}
App plugin example
// src/module.ts
import { AppPlugin } from '@grafana/data';
import { AppConfig } from './components/AppConfig';
import { AppRootPage } from './components/AppRootPage';
export const plugin = new AppPlugin<{}>()
.setRootPage(AppRootPage)
.addConfigPage({
title: 'Configuration',
icon: 'cog',
body: AppConfig,
id: 'configuration',
});
Backend plugin architecture
Backend plugins run as separate processes and communicate with Grafana via gRPC.
Backend structure
From pkg/plugins/plugins.go:
type Plugin struct {
JSONData // Metadata from plugin.json
FS // File system access
Class // Core or external
Signature // Signature validation
client backendplugin.Plugin
}
type PluginClient interface {
backend.QueryDataHandler
backend.CheckHealthHandler
backend.CallResourceHandler
backend.StreamHandler
}
Backend plugin implementation
// pkg/main.go
package main
import (
"context"
"os"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
)
type MyDatasource struct{}
func (d *MyDatasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
response := backend.NewQueryDataResponse()
for _, q := range req.Queries {
// Parse query
var query MyQuery
if err := json.Unmarshal(q.JSON, &query); err != nil {
return nil, err
}
// Execute query and build response
frame := data.NewFrame("response")
frame.Fields = append(frame.Fields,
data.NewField("time", nil, []time.Time{time.Now()}),
data.NewField("value", nil, []float64{42.0}),
)
response.Responses[q.RefID] = backend.DataResponse{
Frames: []*data.Frame{frame},
}
}
return response, nil
}
func (d *MyDatasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
return &backend.CheckHealthResult{
Status: backend.HealthStatusOk,
Message: "Data source is working",
}, nil
}
func main() {
if err := datasource.Manage("myorg-mydatasource-datasource", NewDatasourceInstance, datasource.ManageOpts{}); err != nil {
log.DefaultLogger.Error(err.Error())
os.Exit(1)
}
}
func NewDatasourceInstance(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
return &MyDatasource{}, nil
}
Backend plugin handlers
QueryData handler
Execute data queries:
func (d *MyDatasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
// req contains:
// - Queries: list of queries to execute
// - PluginContext: datasource settings, user info
// - Headers: HTTP headers
// Return DataResponse with Frames
}
CallResource handler
Custom API endpoints:
func (d *MyDatasource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
// req.Path: API path
// req.Method: HTTP method
// req.Body: request body
switch req.Path {
case "metrics":
metrics := getMetrics()
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusOK,
Body: json.Marshal(metrics),
})
}
}
CheckHealth handler
Health checks:
func (d *MyDatasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
// Test connection to data source
if err := testConnection(); err != nil {
return &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: "Connection failed: " + err.Error(),
}, nil
}
return &backend.CheckHealthResult{
Status: backend.HealthStatusOk,
Message: "Data source is working",
}, nil
}
Stream handler
Streaming data:
func (d *MyDatasource) SubscribeStream(ctx context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) {
return &backend.SubscribeStreamResponse{
Status: backend.SubscribeStreamStatusOK,
}, nil
}
func (d *MyDatasource) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
frame := data.NewFrame("stream")
// ... add data to frame
if err := sender.SendFrame(frame, data.IncludeAll); err != nil {
return err
}
}
}
}
Plugin communication
Frontend to backend
Frontend plugins communicate with their backends through the Grafana backend:
// Frontend datasource class
import { DataSourceWithBackend } from '@grafana/runtime';
export class MyDataSource extends DataSourceWithBackend<MyQuery> {
// Queries automatically go to backend
// Custom resource call
async getMetrics() {
return this.getResource('metrics');
}
}
The DataSourceWithBackend class handles:
- Query routing to backend
- Resource calls
- Health checks
- Authentication
Backend to external services
Backend plugins can make external API calls:
import "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
func (d *MyDatasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
// Get HTTP client with proper auth
client, err := httpclient.New(httpclient.Options{})
if err != nil {
return nil, err
}
// Make external API call
resp, err := client.Get("https://api.example.com/data")
// ...
}
Plugin discovery and loading
Plugins are discovered and loaded through a pipeline in pkg/plugins/manager/pipeline/:
Discovery
-
Scan plugin directories:
- Core plugins:
public/app/plugins/
- External plugins:
<data>/plugins/
-
Read
plugin.json from each directory
-
Validate plugin structure
Validation
From pkg/plugins/manager/pipeline/validation/:
type PluginSignatureValidator struct {
// Validates plugin signatures
}
func (v *PluginSignatureValidator) Validate(ctx context.Context, plugin *plugins.Plugin) error {
// Check signature based on plugin type
// - Core plugins: no signature required
// - Bundled plugins: verified at build time
// - External plugins: must be signed
}
Initialization
From pkg/plugins/manager/pipeline/initialization/:
- Load plugin metadata
- Start backend process (if
backend: true)
- Establish gRPC connection
- Register with plugin registry
Registration
type PluginRegistry interface {
Add(ctx context.Context, p *Plugin) error
Remove(ctx context.Context, id string) error
Plugin(ctx context.Context, id string) (*Plugin, bool)
Plugins(ctx context.Context, types ...Type) []*Plugin
}
Plugin signing
External plugins must be signed:
- Private signature - For internal use
- Community signature - For community plugins
- Commercial signature - For commercial plugins
Unsigned plugins require:
# grafana.ini
[plugins]
allow_loading_unsigned_plugins = myplugin-panel
Plugin development workflow
Create plugin
npx @grafana/create-plugin
Build plugin
# Frontend
yarn build
# Backend
mage -v build:linux
Development mode
# Frontend watch mode
yarn dev
# Backend with debugging
mage -v reloadPlugin
Test plugin
# Frontend tests
yarn test
# Backend tests
go test ./pkg/...
# E2E tests
yarn e2e
Plugin examples in Grafana
Built-in panel plugins
Located in public/app/plugins/panel/:
timeseries/ - Time series graph
table/ - Table visualization
stat/ - Single stat display
gauge/ - Gauge visualization
Built-in data source plugins
Located in pkg/tsdb/ (backend) and public/app/plugins/datasource/ (frontend):
pkg/tsdb/prometheus/ # Prometheus backend
public/app/plugins/datasource/prometheus/ # Prometheus frontend
Example: pkg/tsdb/prometheus/:
type Service struct {
im instancemgmt.InstanceManager
}
func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
// Execute Prometheus queries
}
Plugin extensions
Plugins can extend Grafana UI:
// plugin.json
{
"extensions": {
"addedLinks": [
{
"targets": ["grafana/dashboard/panel/menu"],
"title": "Export to MyService",
"description": "Export panel data",
"configure": "exportConfigure"
}
],
"addedComponents": [
{
"targets": ["grafana/dashboard/toolbar"],
"title": "My Button",
"component": "MyButton"
}
]
}
}
Implement in plugin:
export const plugin = new AppPlugin()
.exposeComponent({
id: 'MyButton',
title: 'My Button',
description: 'Custom toolbar button',
component: MyButtonComponent,
});
Key patterns summary
- Frontend plugins - React components with Grafana SDK
- Backend plugins - Go processes with gRPC communication
- Plugin.json - Metadata and configuration
- Discovery pipeline - Automatic plugin loading
- Signature validation - Security through signing
- Extensions - UI extensibility points
- Resource handlers - Custom API endpoints