Creating Custom Plugins
1. Setting Up Plugin Development (@grafana/create-plugin)
| Command | Description | Notes |
|---|---|---|
| npm init @grafana/plugin <plugin-name> | Creates a new Grafana plugin project | Uses the official Grafana plugin template |
| npm run build | Builds the plugin for production | Generates optimized bundles in dist/ |
| npm run dev | Starts the development server | Watches for file changes and rebuilds automatically |
@grafana/create-plugin tool scaffolds a plugin with the latest Grafana plugin structure and dependencies.
2. Creating Panel Plugins
Panel plugins in Grafana allow you to create custom visualizations for dashboards. They are built using the Grafana Plugin SDK and can be developed in TypeScript or JavaScript.
| Property | Description | Default Value |
|---|---|---|
| id | Unique identifier for the panel plugin | None |
| name | Name of the panel plugin | None |
| type | Must be set to "panel" | None |
| version | Version of the plugin | 1.0.0 |
Example: Basic Panel Plugin Structure
import { PanelPlugin } from '@grafana/data';
export const plugin = new PanelPlugin(MyPanel)
.setPanelOptions(builder => {
return builder.addTextInput({
path: 'myOption',
name: 'My Option',
description: 'Description of my option',
});
});
Example: Panel Component
import React from 'react';
import { PanelProps } from '@grafana/data';
interface MyPanelOptions {
myOption: string;
}
export const MyPanel: React.FC<PanelProps<MyPanelOptions>> = ({ options, data }) => {
return (
<div>
<p>My Option Value: {options.myOption}</p>
</div>
);
};
3. Creating Data Source Plugins
Data source plugins in Grafana allow you to connect to external data sources and query them directly from Grafana dashboards. These plugins are essential for extending Grafana's capabilities to work with various data stores.
| Property | Description | Values |
|---|---|---|
| id | Unique identifier for the plugin | my-datasource |
| name | Display name of the plugin | My Data Source |
| type | Must be datasource |
datasource |
| backend | Indicates if the plugin uses backend | true |
Example: Plugin Configuration
{
"id": "my-datasource",
"name": "My Data Source",
"type": "datasource",
"backend": true,
"info": {
"description": "A custom data source plugin",
"version": "1.0.0"
}
}
backend property must be set to true for plugins that require server-side processing.
3. Plugin Structure
A typical data source plugin structure includes the following components:
src: Source code directorydist: Compiled plugin filesmodule.ts: Main module filedatasource.ts: Data source implementationplugin.json: Plugin metadata
Example: Basic Plugin Structure
my-datasource/
├── src/
│ ├── module.ts
│ └── datasource.ts
├── dist/
├── plugin.json
3. Data Source Implementation
The data source implementation handles queries to the external system and returns results in a format Grafana can understand.
| Method | Description | Return Type |
|---|---|---|
| query | Handles query execution | Promise<DataFrame[]> |
| testDatasource | Tests connection to the data source | Promise<any> |
| annotationQuery | Handles annotation queries | Promise<any> |
Example: Query Method
class MyDataSource extends DataSourcePlugin<MyQuery, MyDataSourceOptions> {
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
const { targets } = options;
return targets.map(target => ({
data: [
{
fields: [
{ name: 'time', values: [1, 2, 3] },
{ name: 'value', values: [10, 20, 30] }
]
}
]
}));
}
}
3. Configuration Options
Data source plugins can support various configuration options to allow users to customize their connection.
| Option | Type | Description | Required |
|---|---|---|---|
| url | string |
Endpoint URL for the data source | true |
| authToken | string |
Authentication token for the data source | false |
| timeout | number |
Request timeout in milliseconds | false |
Example: Configuration in Plugin JSON
{
"name": "My Data Source",
"id": "my-datasource",
"type": "datasource",
"jsonData": {
"url": "https://api.example.com",
"timeout": 5000
}
}
4. Creating App Plugins
App plugins in Grafana are extensions that provide a complete application experience within the Grafana interface. They can include dashboards, data sources, panels, and more.
| Property | Description | Value |
|---|---|---|
| plugin.json | Configuration file for the plugin | { "id": "my-app-plugin", "name": "My App Plugin", "type": "app", "version": "1.0.0" } |
| appPlugin | Entry point for the app plugin | src/app.ts |
| rootPath | Root path of the plugin in the Grafana UI | /a/my-app-plugin |
Example: Plugin Configuration
{
"id": "my-app-plugin",
"name": "My App Plugin",
"type": "app",
"version": "1.0.0",
"appPlugin": {
"rootPath": "/a/my-app-plugin"
}
}
Example: App Plugin Entry Point
import { AppPlugin } from '@grafana/data';
import { App } from './components/App';
export const plugin = new AppPlugin<AppSettings>()
.setRootComponent(App);
Key Features of App Plugins
- Can include custom dashboards and data sources
- Supports navigation within the Grafana UI
- Can define custom routes and pages
- Can integrate with Grafana's plugin system
5. Implementing Plugin Options
| Option | Type | Description | Default |
|---|---|---|---|
| pluginId | string |
Unique identifier for the plugin | null |
| name | string |
Display name of the plugin | "Custom Plugin" |
| description | string |
Short description of the plugin's purpose | "A custom Grafana plugin" |
| version | string |
Plugin version (semantic versioning) | "1.0.0" |
| backend | boolean |
Indicates if the plugin has a backend component | false |
Example: Plugin Configuration
{
"pluginId": "my-custom-plugin",
"name": "My Custom Plugin",
"description": "A plugin that visualizes custom data sources",
"version": "2.1.0",
"backend": true
}
pluginId must be unique across all Grafana plugins and follow the format org-pluginName.
pluginId after deployment will break existing dashboards using this plugin.
6. Using Grafana UI Library
The Grafana UI Library provides a set of React components and utilities to help developers build custom plugins with a consistent look and feel that matches Grafana's design language.
| Component | Description | Usage |
|---|---|---|
| Button | A standard button component with multiple variants (primary, secondary, etc.) | <Button variant="primary">Click Me</Button> |
| Card | A container component for grouping related content | <Card><Card.Title>Title</Card.Title></Card> |
| Input | Form input component with validation and styling | <Input placeholder="Enter value" /> |
| Select | A dropdown selector component | <Select options={options} onChange={handleChange} /> |
| Switch | A toggle switch for binary options | <Switch checked={value} onChange={handleChange} /> |
Example: Using Button Component
import { Button } from "@grafana/ui";
function MyComponent() {
return (
<Button variant="primary" onClick={() => console.log("Clicked!")}>
Primary Button
</Button>
);
}
Example: Using Card Component
import { Card, CardBody, CardTitle } from "@grafana/ui";
function MyCard() {
return (
<Card>
<CardTitle>Dashboard Card</CardTitle>
<CardBody>
<p>This is a sample card content.</p>
</CardBody>
</Card>
);
}
@grafana/ui package in your plugin project to use these components.
7. Using Plugin Data API
| Method | Description | Parameters | Return Type |
|---|---|---|---|
| query | Fetches data from a data source | options: QueryOptions |
Promise<DataFrame[]> |
| annotationQuery | Fetches annotation data for a dashboard | options: AnnotationQueryOptions |
Promise<AnnotationEvent[]> |
| metricFindQuery | Performs a metric find query to return a list of strings or objects | query: string |
Promise<MetricFindValue[]> |
Example: Querying Data
import { DataQueryRequest, DataQueryResponse } from '@grafana/data';
export class MyDataSource extends DataSourceApi<MyQuery> {
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
const { targets } = options;
const results = await Promise.all(
targets.map(async (target) => {
const response = await this.backendSrv.get(`/api/plugins/my-plugin/resources/data`, {
params: { query: target.query },
});
return {
refId: target.refId,
data: response.data,
};
})
);
return { data: results };
}
}
Example: Annotation Query
import { AnnotationQueryRequest } from '@grafana/data';
export class MyAnnotationPlugin extends DataSourceApi<MyAnnotationQuery> {
async annotationQuery(options: AnnotationQueryRequest<MyAnnotationQuery>): Promise<AnnotationEvent[]> {
const response = await this.backendSrv.get(`/api/plugins/my-plugin/resources/annotations`, {
params: { timeFrom: options.range.from.unix(), timeTo: options.range.to.unix() },
});
return response.data.map((event: any) => ({
...event,
time: event.time.unix(),
}));
}
}
Example: Metric Find Query
export class MyMetricFindQueryPlugin extends DataSourceApi<MyQuery> {
async metricFindQuery(query: string): Promise<MetricFindValue[]> {
const response = await this.backendSrv.get(`/api/plugins/my-plugin/resources/metrics`, {
params: { query },
});
return response.data.map((item: any) => ({
text: item.name,
value: item.id,
}));
}
}
8. Testing Plugins Locally
| Step | Description | Command |
|---|---|---|
| 1 | Start the Grafana development server | npm run dev |
| 2 | Build the plugin for development | npm run build |
| 3 | Watch for code changes and rebuild automatically | npm run watch |
Example: Plugin Development Setup
mkdir my-grafana-plugin
cd my-grafana-plugin
npm init -y
npm install @grafana/cli @grafana/data @grafana/runtime
plugins directory.
npm install inside the plugin directory as it may cause dependency conflicts.
Testing Workflow Summary
- Use
npm run devto start Grafana in development mode. - Use
npm run buildto compile the plugin code. - Use
npm run watchfor automatic rebuilding on file changes.
9. Building Plugins
Example: Basic Plugin Structure
// plugin.json
{
"id": "myorg-myplugin",
"name": "My Plugin",
"type": "panel",
"version": "1.0.0",
"backend": false,
"dependencies": {
"grafanaVersion": "^10.0.0"
}
}
| Field | Description | Required |
|---|---|---|
| id | Unique identifier for the plugin | Yes |
| name | Display name of the plugin | Yes |
| type | Plugin type (panel, datasource, app) | Yes |
| version | Plugin version (semantic versioning) | Yes |
| backend | Whether the plugin uses a backend server | No |
| dependencies.grafanaVersion | Minimum Grafana version supported | Yes |
Example: Panel Plugin Component
// src/panel.ts
import { PanelPlugin } from "@grafana/data";
export const plugin = new PanelPlugin(MyPanel)
.setPanelOptions(builder => {
return builder.addTextInput({
path: "showTitle",
name: "Show Title",
description: "Toggle title display",
defaultValue: true
});
});
Example: Datasource Plugin
// src/datasource.ts
import { DataSourceApi } from "@grafana/data";
export class MyDataSource extends DataSourceApi<MyQuery> {
constructor(instanceSettings: DataSourceInstanceSettings) {
super(instanceSettings);
}
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
// Query logic here
}
}
Plugin Development Workflow
- Create plugin directory structure
- Define
plugin.json - Implement plugin logic (panel, datasource, app)
- Build with
npm run build - Test in Grafana instance
- Deploy to Grafana Plugin Registry (optional)
10. Signing Plugins
| Field | Description | Value |
|---|---|---|
| plugin.json | Plugin manifest file | { "signature": "unsigned" } |
| grafana-sign | CLI tool for signing plugins | grafana-sign sign --pluginDir ./my-plugin |
Example: Signing a Plugin
grafana-sign sign --pluginDir ./my-plugin --keyFile /path/to/key.pem --certFile /path/to/cert.pem
signature field set to valid in its manifest.
| Signature Status | Description | Impact |
|---|---|---|
unsigned |
No signature present | Plugin not loaded in secure environments |
valid |
Signature verified successfully | Plugin loaded normally |
invalid |
Signature verification failed | Plugin not loaded, error logged |