Skip to main content

Building Custom Plugins

Now that you've mastered using Grafana's extensive plugin ecosystem, it's time to learn how to extend Grafana's capabilities by building your own custom plugins. This lesson will guide you through creating a complete plugin from scratch.

Learning Goals:

  • Understand the Grafana plugin architecture
  • Set up a plugin development environment
  • Create a custom panel plugin
  • Package and distribute your plugin

Plugin Architecture Overview

Grafana plugins extend functionality in several ways:

  • Panel Plugins: Add new visualization types
  • Data Source Plugins: Connect to new data sources
  • App Plugins: Comprehensive applications with multiple panels
tip

Start with panel plugins—they're the easiest to develop and provide immediate visual feedback for testing.

Setting Up Your Development Environment

First, ensure you have the necessary tools installed:

Install required tools
npm install -g @grafana/create-plugin
node --version # Requires Node.js 16+

Create your first plugin:

Create a new plugin
npx @grafana/create-plugin@latest
cd my-custom-panel
npm run dev

This starts a development server and opens Grafana with your plugin loaded.

Building a Simple Panel Plugin

Let's create a "Hello World" panel that displays custom text:

src/module.ts
import { PanelPlugin } from '@grafana/data';
import { SimpleOptions } from './types';
import { SimplePanel } from './SimplePanel';

export const plugin = new PanelPlugin<SimpleOptions>(SimplePanel)
.setPanelOptions(builder => {
return builder
.addTextInput({
path: 'text',
name: 'Display text',
description: 'Text to display in the panel',
defaultValue: 'Hello, Grafana!',
})
.addColorPicker({
path: 'color',
name: 'Text color',
defaultValue: 'red',
});
});
src/SimplePanel.tsx
import React from 'react';
import { PanelProps } from '@grafana/data';
import { SimpleOptions } from './types';

interface Props extends PanelProps<SimpleOptions> {}

export const SimplePanel: React.FC<Props> = ({ options, data, width, height }) => {
return (
<div
style={{
width,
height,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<h1 style={{ color: options.color }}>{options.text}</h1>
</div>
);
};

Adding Data Processing

Most plugins need to process data. Here's how to handle data frames:

src/dataProcessing.ts
import { FieldType, DataFrame } from '@grafana/data';

export function processData(frames: DataFrame[]) {
if (!frames || frames.length === 0) {
return { processed: false, message: 'No data available' };
}

const frame = frames[0];
const timeField = frame.fields.find(f => f.type === FieldType.time);
const valueField = frame.fields.find(f => f.type === FieldType.number);

if (!timeField || !valueField) {
return { processed: false, message: 'Required fields not found' };
}

// Calculate simple statistics
const values = valueField.values.toArray();
const sum = values.reduce((a, b) => a + b, 0);
const average = sum / values.length;

return {
processed: true,
dataPoints: values.length,
average,
min: Math.min(...values),
max: Math.max(...values),
};
}

Plugin Configuration

Configure your plugin in the plugin.json file:

src/plugin.json
{
"type": "panel",
"name": "My Custom Panel",
"id": "myorg-custom-panel",

"info": {
"description": "A custom panel plugin for Grafana",
"author": {
"name": "Your Name",
"url": "https://yourwebsite.com"
},
"keywords": ["panel", "custom"],
"logos": {
"small": "img/logo.svg",
"large": "img/logo.svg"
},
"links": [
{"name": "Website", "url": "https://yourwebsite.com"},
{"name": "License", "url": "https://yourwebsite.com/license"}
],
"screenshots": [],
"version": "1.0.0",
"updated": "2024-01-01"
},

"dependencies": {
"grafanaDependency": ">=9.0.0",
"plugins": []
}
}

Testing Your Plugin

Create unit tests for your plugin components:

src/SimplePanel.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import { SimplePanel } from './SimplePanel';
import { FieldType, toDataFrame } from '@grafana/data';

const defaultProps = {
options: { text: 'Test Text', color: 'blue' },
data: { series: [toDataFrame({ fields: [] })] },
width: 400,
height: 300,
timeRange: {} as any,
timeZone: 'utc',
onChangeTimeRange: jest.fn(),
onFieldConfigChange: jest.fn(),
onOptionsChange: jest.fn(),
replaceVariables: jest.fn(),
eventBus: {} as any,
fieldConfig: {} as any,
id: 1,
transparent: false,
renderCounter: 0,
title: 'Test Panel',
};

describe('SimplePanel', () => {
it('should render with provided text', () => {
render(<SimplePanel {...defaultProps} />);
expect(screen.getByText('Test Text')).toBeInTheDocument();
});

it('should apply custom color', () => {
const { container } = render(<SimplePanel {...defaultProps} />);
const heading = container.querySelector('h1');
expect(heading).toHaveStyle('color: blue');
});
});

Run tests with:

npm test

Building and Packaging

When ready to distribute, build your plugin:

Build for production
npm run build

This creates a dist directory containing your packaged plugin. For distribution:

Create package
npm run sign
warning

Always sign your plugins before distribution. Unsigned plugins may be blocked in production Grafana instances for security reasons.

Common Pitfalls

  • Missing Dependencies: Ensure all dependencies are listed in package.json
  • TypeScript Errors: Use strict type checking during development
  • Performance Issues: Avoid heavy computations in render methods
  • Memory Leaks: Clean up event listeners and subscriptions
  • Version Compatibility: Test with multiple Grafana versions
  • Security: Validate all user inputs and external data
  • Error Handling: Implement proper error boundaries and fallbacks

Summary

You've learned how to create custom Grafana plugins from setting up the development environment to building, testing, and packaging. Remember to start simple, test thoroughly, and follow Grafana's plugin guidelines for the best user experience.

Show quiz
  1. What command creates a new Grafana plugin project?

    • A) grafana create-plugin
    • B) npx @grafana/create-plugin
    • C) npm init grafana-plugin
    • D) yarn create grafana-plugin
  2. Which file defines the plugin's metadata and configuration?

    • A) package.json
    • B) plugin.json
    • C) config.ts
    • D) manifest.json
  3. What is the purpose of the setPanelOptions method?

    • A) To configure data source connections
    • B) To define user-configurable settings
    • C) To set panel dimensions
    • D) To configure authentication
  4. Why is plugin signing important?

    • A) It improves performance
    • B) It's required for security in production
    • C) It enables premium features
    • D) It's only needed for enterprise plugins
  5. What should you test in your panel plugin?

    • A) Only the visual rendering
    • B) Only data processing
    • C) Both rendering and data handling
    • D) Only error cases

Answers:

  1. B) npx @grafana/create-plugin
  2. B) plugin.json
  3. B) To define user-configurable settings
  4. B) It's required for security in production
  5. C) Both rendering and data handling