Configure custom Grafana plugins for specialized monitoring requirements

Advanced 45 min Apr 23, 2026 439 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Build custom Grafana data source and panel plugins from scratch, then deploy them securely in production environments with proper authentication and access controls.

Prerequisites

  • Existing Grafana installation
  • Node.js 18+ development environment
  • Go 1.19+ for backend plugins
  • Basic React and TypeScript knowledge

What this solves

Standard Grafana plugins cover most monitoring scenarios, but specialized environments often need custom data sources or unique visualizations. This tutorial walks you through creating custom Grafana plugins for proprietary systems, internal APIs, or specialized visualization requirements that aren't available in the community plugin catalog.

Prerequisites and environment setup

Install Node.js development environment

Grafana plugins require Node.js 18+ and specific development tools for building and packaging.

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs git
npm install -g yarn @grafana/toolkit
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
sudo dnf install -y nodejs npm git
npm install -g yarn @grafana/toolkit

Set up Grafana development instance

Install a local Grafana instance for plugin development and testing.

sudo apt-get install -y apt-transport-https software-properties-common wget
sudo mkdir -p /etc/apt/keyrings/
wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list
sudo apt-get update
sudo apt-get install grafana
sudo tee /etc/yum.repos.d/grafana.repo <

Enable development mode

Configure Grafana for plugin development with unsigned plugins enabled and proper permissions.

[paths]
plugins = /var/lib/grafana/plugins

[plugins]
allow_loading_unsigned_plugins = true
enable_alpha = true

[security]
allow_embedding = true

[development]
mode = true
sudo systemctl enable --now grafana-server
sudo systemctl status grafana-server

Creating a custom data source plugin

Initialize the data source plugin project

Use the Grafana toolkit to scaffold a new data source plugin with proper TypeScript structure.

mkdir -p ~/grafana-plugins
cd ~/grafana-plugins
npx @grafana/create-plugin@latest custom-api-datasource
cd custom-api-datasource

Configure plugin metadata

Define the plugin information and capabilities in the plugin.json manifest file.

{
  "type": "datasource",
  "name": "Custom API DataSource",
  "id": "custom-api-datasource",
  "category": "cloud",
  "info": {
    "description": "Custom data source for proprietary API endpoints",
    "author": {
      "name": "Your Organization"
    },
    "version": "1.0.0",
    "updated": "2024-01-01"
  },
  "includes": [
    {
      "type": "datasource",
      "name": "Custom API"
    }
  ],
  "metrics": true,
  "annotations": false,
  "logs": false,
  "alerting": true,
  "backend": true,
  "executable": "gpx_custom-api-datasource"
}

Implement the data source backend

Create the Go backend that handles data queries and authentication with your custom API.

package plugin

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/grafana/grafana-plugin-sdk-go/backend"
	"github.com/grafana/grafana-plugin-sdk-go/data"
)

type Datasource struct {
	settings backend.DataSourceInstanceSettings
	httpClient *http.Client
}

func NewDatasource(settings backend.DataSourceInstanceSettings) (backend.DataSourcePlugin, error) {
	return &Datasource{
		settings: settings,
		httpClient: &http.Client{Timeout: 30 * time.Second},
	}, nil
}

func (d *Datasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
	response := backend.NewQueryDataResponse()

	for _, q := range req.Queries {
		res := d.query(ctx, req.PluginContext, q)
		response.Responses[q.RefID] = res
	}

	return response, nil
}

func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, query backend.DataQuery) backend.DataResponse {
	var qm QueryModel
	if err := json.Unmarshal(query.JSON, &qm); err != nil {
		return backend.ErrDataResponse(backend.StatusBadRequest, fmt.Sprintf("json unmarshal: %v", err.Error()))
	}

	// Create data frame for response
	frame := data.NewFrame("response")
	frame.Fields = append(frame.Fields,
		data.NewField("Time", nil, []time.Time{query.TimeRange.From, query.TimeRange.To}),
		data.NewField("Value", nil, []float64{42.0, 84.0}),
	)

	return backend.DataResponse{
		Frames: []*data.Frame{frame},
	}
}

Implement frontend configuration

Create the React components for configuring the data source in Grafana's UI.

import React, { ChangeEvent } from 'react';
import { InlineField, Input, SecretInput } from '@grafana/ui';
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { CustomDataSourceOptions, CustomSecureJsonData } from './types';

interface Props extends DataSourcePluginOptionsEditorProps

Create query editor component

Build the interface for users to configure queries when creating dashboards.

import React, { ChangeEvent } from 'react';
import { InlineField, Input, Select } from '@grafana/ui';
import { QueryEditorProps, SelectableValue } from '@grafana/data';
import { DataSource } from './datasource';
import { CustomDataSourceOptions, CustomQuery } from './types';

type Props = QueryEditorProps

Creating a custom panel plugin

Initialize panel plugin project

Create a new panel plugin for specialized data visualization requirements.

cd ~/grafana-plugins
npx @grafana/create-plugin@latest custom-status-panel --pluginType=panel
cd custom-status-panel

Implement the panel component

Build a React component that renders your custom visualization using Grafana's data and theme APIs.

<code>import React from 'react';
import { PanelProps } from '@grafana/data';
import { useStyles2, useTheme2 } from '@grafana/ui';
import { css } from '@emotion/css';
import { StatusOptions } from 'types';

interface Props extends PanelProps</code><div><code>
      </code><div><code>${styles.status} ${statusClass}</code>}>
        {status}
      </div>
      <div>
        {currentValue.toFixed(2)} {options.unit}
      </div>
    </div>
  );
};

Add panel options editor

Create configuration options that users can adjust in the panel settings.

import { PanelPlugin } from '@grafana/data';
import { StatusOptions } from './types';
import { StatusPanel } from './components/StatusPanel';

export const plugin = new PanelPlugin

Plugin security hardening and deployment

Build and package plugins

Compile both plugins for production deployment with proper optimization.

cd ~/grafana-plugins/custom-api-datasource
npm run build
npm run sign

cd ~/grafana-plugins/custom-status-panel
npm run build
npm run sign

Configure plugin security

Set up proper permissions and security policies for plugin installation. This involves understanding both Grafana RBAC configuration and how to properly secure custom plugin installations.

[plugins]
allow_loading_unsigned_plugins = custom-api-datasource,custom-status-panel
plugin_admin_enabled = true
plugin_admin_external_manage_enabled = false

[security]
cookie_secure = true
cookie_samesite = strict
strict_transport_security = true
strict_transport_security_max_age_seconds = 86400
strict_transport_security_preload = true

Deploy plugins to production

Install plugins in the proper directory with correct ownership and permissions.

sudo mkdir -p /var/lib/grafana/plugins
sudo cp -r ~/grafana-plugins/custom-api-datasource/dist /var/lib/grafana/plugins/custom-api-datasource
sudo cp -r ~/grafana-plugins/custom-status-panel/dist /var/lib/grafana/plugins/custom-status-panel
sudo chown -R grafana:grafana /var/lib/grafana/plugins
sudo chmod -R 755 /var/lib/grafana/plugins
Never use chmod 777. It gives every user on the system full access to your plugin files. The grafana user needs read/execute permissions, which 755 provides safely.

Configure plugin validation

Set up checksum validation and update mechanisms for production plugin management.

# Plugin integrity checks
[[servers]]
host = "127.0.0.1"
port = 389
use_ssl = false
start_tls = false
bind_dn = "cn=admin,dc=example,dc=com"
bind_password = "secure_password"
search_filter = "(cn=%s)"
search_base_dns = ["dc=example,dc=com"]

[servers.attributes]
name = "givenName"
surname = "sn"
username = "cn"
member_of = "memberOf"
email = "mail"

# Plugin access control
[[servers.group_mappings]]
group_dn = "cn=grafana-plugin-admins,ou=groups,dc=example,dc=com"
org_role = "Admin"

[[servers.group_mappings]]
group_dn = "cn=grafana-users,ou=groups,dc=example,dc=com"
org_role = "Viewer"

Restart and verify deployment

Restart Grafana and confirm plugins load correctly with proper security settings.

sudo systemctl restart grafana-server
sudo systemctl status grafana-server
sudo journalctl -u grafana-server -f --lines=50

Advanced plugin development patterns

Implement plugin testing

Add comprehensive testing for both frontend and backend plugin components.

import React from 'react';
import { render, screen } from '@testing-library/react';
import { StatusPanel } from '../components/StatusPanel';
import { PanelProps } from '@grafana/data';
import { StatusOptions } from '../types';

const mockProps: PanelProps

Add plugin logging and monitoring

Implement structured logging for plugin debugging and performance monitoring. This complements broader Grafana monitoring strategies by providing plugin-specific insights.

import (
	"github.com/grafana/grafana-plugin-sdk-go/backend/log"
)

func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, query backend.DataQuery) backend.DataResponse {
	logger := log.DefaultLogger
	logger.Info("Processing query", "refId", query.RefID, "timeRange", query.TimeRange)

	start := time.Now()
	defer func() {
		duration := time.Since(start)
		logger.Info("Query completed", "refId", query.RefID, "duration", duration)
	}()

	// Query implementation...
	return backend.DataResponse{Frames: frames}
}

Verify your setup

# Check plugin installation
curl -u admin:admin http://localhost:3000/api/plugins | jq '.[] | select(.id=="custom-api-datasource" or .id=="custom-status-panel")'

# Verify plugin functionality
curl -X POST -u admin:admin -H "Content-Type: application/json" \
  -d '{"datasource":{"type":"custom-api-datasource"}}' \
  http://localhost:3000/api/ds/query

# Check plugin logs
sudo journalctl -u grafana-server | grep -i plugin

# Verify permissions
sudo find /var/lib/grafana/plugins -type f -exec ls -la {} \;

Common issues

SymptomCauseFix
Plugin not loadingIncorrect permissions or unsigned pluginFix ownership with chown grafana:grafana and verify allow_loading_unsigned_plugins
Fr

Automated install script

Run this to automate the entire setup

Wil je dit niet zelf beheren?

Wij beheren infrastructuur voor bedrijven die afhankelijk zijn van uptime. Volledig beheerd, met één vast aanspreekpunt dat je omgeving kent.

U krijgt één vast aanspreekpunt dat uw omgeving kent

Rotterdam 19:03 · bereikbaar in een bericht, geen ticketformulier