### Getting Started Commands for New Plugin
Source: https://grafana.com/developers/plugin-tools
After scaffolding a new plugin, use these commands to install dependencies, build frontend and backend code, and run the Grafana development server.
```bash
cd ./orgName-pluginName-app
npm install to install frontend dependencies.
npm exec playwright install chromium to install e2e test dependencies.
npm run dev to build (and watch) the plugin frontend code.
mage -v build:backend to build the plugin backend code. Rerun this command every time you edit your backend files.
docker compose up to start a grafana development server.
Open http://localhost:3000 in your browser to create a dashboard to begin developing your plugin.
```
--------------------------------
### Install dependencies
Source: https://grafana.com/developers/plugin-tools/how-to-guides/app-plugins/add-backend-component
Install the necessary Node.js dependencies for the plugin.
```shell
npm install
```
--------------------------------
### Implement interactive query examples
Source: https://grafana.com/developers/plugin-tools/how-to-guides/data-source-plugins/add-query-editor-help
Create a list of query examples and use the onClickExample prop to update the query editor when an item is clicked.
```tsx
import React from 'react';
import { QueryEditorHelpProps, DataQuery } from '@grafana/data';
const examples = [
{
title: 'Addition',
expression: '1 + 2',
label: 'Add two integers',
},
{
title: 'Subtraction',
expression: '2 - 1',
label: 'Subtract an integer from another',
},
];
export default (props: QueryEditorHelpProps) => {
return (
Cheat Sheet
{examples.map((item, index) => (
{item.title}
{item.expression ? (
props.onClickExample({ refId: 'A', queryText: item.expression } as DataQuery)}
>
{item.expression}
) : null}
{item.label}
))}
);
};
```
--------------------------------
### Install Plugin Dependencies
Source: https://grafana.com/developers/plugin-tools/tutorials/build-ai-plugin
Navigate to the plugin directory and install the required Node.js dependencies for the plugin.
```sh
cd ./myorg-bcapi-datasource
npm install
```
--------------------------------
### Install App Plugin Dependencies
Source: https://grafana.com/developers/plugin-tools/tutorials/build-ai-plugin
Command to navigate into the app plugin directory and install its dependencies.
```sh
cd myorg-bcapi-app
npm install
```
--------------------------------
### Build the plugin frontend
Source: https://grafana.com/developers/plugin-tools/publish-a-plugin/package-a-plugin
Commands to install dependencies and build the plugin using different package managers.
```shell
npm install
npm run build
```
```shell
yarn install
yarn build
```
```shell
pnpm install
pnpm run build
```
--------------------------------
### Build plugin frontend
Source: https://grafana.com/developers/plugin-tools/tutorials/build-a-streaming-data-source-plugin
Install dependencies and compile the frontend assets into the dist directory.
```shell
npm install
npm run build
```
--------------------------------
### SQL query template examples
Source: https://grafana.com/developers/plugin-tools/how-to-guides/data-source-plugins/add-support-for-variables
Examples of raw and interpolated SQL query strings.
```sql
SELECT * FROM services WHERE id = "$service"
```
```sql
SELECT * FROM services WHERE id = "auth-api"
```
--------------------------------
### Start Grafana Server with npm
Source: https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/get-started
Use this command to start the Grafana development server when using npm as your package manager.
```shell
npm run server
```
--------------------------------
### Example Plugin Manifest (MANIFEST.txt)
Source: https://grafana.com/developers/plugin-tools/publish-a-plugin/sign-a-plugin
This is an example of the MANIFEST.txt file required for Grafana plugin verification. It includes plugin metadata, file checksums, and a digital signature.
```txt
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
{
"manifestVersion": "2.0.0",
"signatureType": "community",
"signedByOrg": "myorgid",
"signedByOrgName": "My Org",
"plugin": "myorgid-simple-panel",
"version": "1.0.0",
"time": 1602753404133,
"keyId": "7e4d0c6a708866e7",
"files": {
"LICENSE": "12ab7a0961275f5ce7a428e662279cf49bab887d12b2ff7bfde738346178c28c",
"module.js.LICENSE.txt": "0d8f66cd4afb566cb5b7e1540c68f43b939d3eba12ace290f18abc4f4cb53ed0",
"module.js.map": "8a4ede5b5847dec1c6c30008d07bef8a049408d2b1e862841e30357f82e0fa19",
"plugin.json": "13be5f2fd55bee787c5413b5ba6a1fae2dfe8d2df6c867dadc4657b98f821f90",
"README.md": "2d90145b28f22348d4f50a81695e888c68ebd4f8baec731fdf2d79c8b187a27f",
"module.js": "b4b6945bbf3332b08e5e1cb214a5b85c82557b292577eb58c8eb1703bc8e4577"
}
}
-----BEGIN PGP SIGNATURE-----
Version: OpenPGP.js v4.10.1
Comment: https://openpgpjs.org
wqEEARMKAAYFAl+IE3wACgkQfk0ManCIZudpdwIHTCqjVzfm7DechTa7BTbd
+dNIQtwh8Tv2Q9HksgN6c6M9nbQTP0xNHwxSxHOI8EL3euz/OagzWoiIWulG
7AQo7FYCCQGucaLPPK3tsWaeFqVKy+JtQhrJJui23DAZLSYQYZlKQ+nFqc9x
T6scfmuhWC/TOcm83EVoCzIV3R5dOTKHqkjIUg==
=GdNq
-----END PGP SIGNATURE-----
```
--------------------------------
### Example plugin.json Configuration
Source: https://grafana.com/developers/plugin-tools/tutorials/build-an-app-plugin
The `plugin.json` file provides essential metadata about your plugin, including its type, name, and unique ID.
```json
{
"type": "app",
"name": "My App Plugin",
"id": "my-org-my-app-plugin"
}
```
--------------------------------
### Start Grafana
Source: https://grafana.com/developers/plugin-tools/how-to-guides/app-plugins/add-backend-component
Launch the Grafana environment using Docker Compose.
```shell
docker compose up
```
--------------------------------
### Initialize and build a Grafana plugin
Source: https://grafana.com/developers/plugin-tools/tutorials/build-a-data-source-plugin
Commands to scaffold, install, and run a new plugin project using the create-plugin CLI.
```shell
npx @grafana/create-plugin@latest
```
```shell
cd
```
```shell
npm install
```
```shell
npm run dev
```
```shell
docker compose up
```
--------------------------------
### Start Specific Grafana Version with npm
Source: https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/get-started
Specify a Grafana version to start the development server, useful for testing compatibility.
```bash
GRAFANA_VERSION=10.4.1 npm run server
```
--------------------------------
### Install Frontend Dependencies with pnpm
Source: https://grafana.com/developers/plugin-tools
Use this command to install frontend dependencies for your plugin when using pnpm as your package manager.
```bash
cd --
pnpm install
```
--------------------------------
### Example CHANGELOG.md format
Source: https://grafana.com/developers/plugin-tools/publish-a-plugin/publishing-best-practices
A template for organizing release notes by version, date, and change type using semantic versioning.
```markdown
### [1.10.0](https://github.com/user/plugin-name/tree/1.10.0) (2025-04-05)
**Implemented enhancements:**
- Add support for dark theme [\#138](https://github.com/user/plugin-name/pull/138) ([username](https://github.com/username))
- Add ability to customize tooltip formats [\#135](https://github.com/user/plugin-name/pull/135) ([username](https://github.com/username))
- Support for PostgreSQL data source [\#129](https://github.com/user/plugin-name/pull/129) ([username](https://github.com/username))
**Fixed bugs:**
- Fix panel crash when switching dashboards [\#139](https://github.com/user/plugin-name/pull/139) ([username](https://github.com/username))
- Fix inconsistent time zone handling [\#134](https://github.com/user/plugin-name/pull/134) ([username](https://github.com/username))
**Closed issues:**
- Documentation needs examples for PostgreSQL queries [\#130](https://github.com/user/plugin-name/issues/130)
**Merged pull requests:**
- Update dependencies to address security vulnerabilities [\#140](https://github.com/user/plugin-name/pull/140) ([username](https://github.com/username))
**Breaking changes:**
- Migrate configuration storage format [\#115](https://github.com/user/plugin-name/pull/115) ([username](https://github.com/username))
```
--------------------------------
### Example Translation File Structure
Source: https://grafana.com/developers/plugin-tools/how-to-guides/plugin-internationalization
An example of a generated translation file (`src/locales/en-US/[your-plugin-id].json`) showing nested keys for options and their translated values.
```json
{
"components": {
"simplePanel": {
"options": {
"showSeriesCount": "Number of series: {{numberOfSeries}}",
"textOptionValue": "Text option value: {{optionValue}}"
}
}
},
"panel": {
"options": {
"seriesCountSize": {
"name": "Series counter size",
"options": {
"lg": "Large",
"md": "Medium",
"sm": "Small"
}
},
"showSeriesCount": {
"name": "Show series counter"
},
"text": {
"defaultValue": "Default value of text input option",
"description": "Description of panel option",
"name": "Simple text option"
}
}
}
}
```
--------------------------------
### Start Grafana Server with pnpm
Source: https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/get-started
Use this command to start the Grafana development server when using pnpm as your package manager.
```shell
pnpm run server
```
--------------------------------
### Install i18next-cli with pnpm
Source: https://grafana.com/developers/plugin-tools/how-to-guides/plugin-internationalization
Install the `i18next-cli` development dependency using pnpm.
```shell
pnpm add --save-dev i18next-cli
```
--------------------------------
### Start Grafana Server with Yarn
Source: https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/get-started
Use this command to start the Grafana development server when using Yarn as your package manager.
```shell
yarn server
```
--------------------------------
### Install LLM package
Source: https://grafana.com/developers/plugin-tools/how-to-guides/app-plugins/use-llms-and-mcp
Install the required dependency for LLM integration.
```bash
npm install @grafana/llm
```
--------------------------------
### Install i18next-cli with npm
Source: https://grafana.com/developers/plugin-tools/how-to-guides/plugin-internationalization
Install the `i18next-cli` development dependency using npm.
```shell
npm install --save-dev i18next-cli
```
--------------------------------
### Install Frontend Dependencies with Yarn
Source: https://grafana.com/developers/plugin-tools
Use this command to install frontend dependencies for your plugin when using Yarn as your package manager.
```bash
cd --
yarn install
```
--------------------------------
### Install Frontend Dependencies with npm
Source: https://grafana.com/developers/plugin-tools
Use this command to install frontend dependencies for your plugin when using npm as your package manager.
```bash
cd --
npm install
```
--------------------------------
### Start Grafana Development Server with Docker
Source: https://grafana.com/developers/plugin-tools/tutorials/build-ai-plugin
Alternatively, start the Grafana development server directly using Docker Compose. This is useful for managing the Grafana environment.
```sh
docker compose up
```
--------------------------------
### Build Backend Binary with Mage
Source: https://grafana.com/developers/plugin-tools/tutorials/build-an-app-plugin
If your app plugin includes a backend, use the 'mage -v' command to build the binary before starting Grafana.
```shell
mage -v
```
--------------------------------
### Extending Folder Edit Action Set Example
Source: https://grafana.com/developers/plugin-tools/how-to-guides/app-plugins/implement-rbac-in-app-plugins
An example demonstrating how to extend the existing `folders:edit` action set within a plugin's configuration. This grants users with folder edit access additional plugin-specific actions.
```json
{
"actionSets": [
{
"action": "folders:edit",
"actions": [
"my-plugin.docs:create",
"my-plugin.docs:edit"
]
}
],
...
}
```
--------------------------------
### Configure authentication for RBAC-enabled plugins
Source: https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/use-authentication
Defines a setup project to create and authenticate a user with a specific role, then reuses that state for role-specific tests.
```ts
import { dirname } from 'path';
import { defineConfig, devices } from '@playwright/test';
const pluginE2eAuth = `${dirname(require.resolve('@grafana/plugin-e2e'))}/auth`;
export default defineConfig({
...
projects: [
{
name: 'createViewerUserAndAuthenticate',
testDir: pluginE2eAuth,
testMatch: [/.*auth\.setup\.ts/],
use: {
user: {
user: 'viewer',
password: 'password',
role: 'Viewer',
},
},
},
{
name: 'run-tests-for-viewer',
testDir: './tests/viewer',
use: {
...devices['Desktop Chrome'],
// @grafana/plugin-e2e writes the auth state to this file,
// the path should not be modified
storageState: 'playwright/.auth/viewer.json',
},
dependencies: ['createViewerUserAndAuthenticate'],
},
]
})
```
--------------------------------
### Configure authentication for non-RBAC plugins
Source: https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/use-authentication
Uses a setup project to authenticate as admin and reuses the state for subsequent test projects.
```ts
import { dirname } from 'path';
import { defineConfig, devices } from '@playwright/test';
const pluginE2eAuth = `${dirname(require.resolve('@grafana/plugin-e2e'))}/auth`;
export default defineConfig({
...
projects: [
{
name: 'auth',
testDir: pluginE2eAuth,
testMatch: [/.*\.js/],
},
{
name: 'run-tests',
use: {
...devices['Desktop Chrome'],
// @grafana/plugin-e2e writes the auth state to this file,
// the path should not be modified
storageState: 'playwright/.auth/admin.json',
},
dependencies: ['auth'],
}
],
});
```
--------------------------------
### Build and Run Plugin in Development Mode
Source: https://grafana.com/developers/plugin-tools/tutorials/build-an-app-plugin
Build the plugin and start Grafana using Docker Compose for local development and testing.
```shell
npm run dev
```
```shell
docker compose up
```
--------------------------------
### Start Grafana development server with specific version
Source: https://grafana.com/developers/plugin-tools/set-up/set-up-docker
Set the GRAFANA_VERSION environment variable to test the plugin against a specific Grafana release.
```shell
GRAFANA_VERSION=10.0.0 npm run server
```
```shell
GRAFANA_VERSION=10.0.0 yarn server
```
```shell
GRAFANA_VERSION=10.0.0 pnpm run server
```
--------------------------------
### Create Custom Spans with Attributes
Source: https://grafana.com/developers/plugin-tools/how-to-guides/data-source-plugins/add-logs-metrics-traces-for-backend-plugins
Start a new span using the default tracer and attach relevant query metadata as attributes.
```go
func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, query backend.DataQuery) (backend.DataResponse, error) {
ctx, span := tracing.DefaultTracer().Start(
ctx,
"query processing",
trace.WithAttributes(
attribute.String("query.ref_id", query.RefID),
attribute.String("query.type", query.QueryType),
attribute.Int64("query.max_data_points", query.MaxDataPoints),
attribute.Int64("query.interval_ms", query.Interval.Milliseconds()),
attribute.Int64("query.time_range.from", query.TimeRange.From.Unix()),
attribute.Int64("query.time_range.to", query.TimeRange.To.Unix()),
),
)
defer span.End()
// ...
}
```
--------------------------------
### E2E Tests CI Workflow with npm
Source: https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/ci
This workflow configures E2E tests for a Grafana plugin using npm for dependency management. It checks out code, resolves Grafana versions, sets up Node.js, installs npm dependencies, installs Mage, builds binaries and frontend, installs Playwright browsers, starts Grafana, waits for the server, and runs Playwright tests.
```yaml
name: E2E tests
on:
pull_request:
schedule:
- cron: '0 11 * * *' #Run e2e tests once a day at 11 UTC
permissions:
contents: read
jobs:
resolve-versions:
name: Resolve Grafana images
runs-on: ubuntu-latest
timeout-minutes: 3
outputs:
matrix: ${{ steps.resolve-versions.outputs.matrix }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Resolve Grafana E2E versions
id: resolve-versions
uses: grafana/plugin-actions/e2e-version@main
playwright-tests:
needs: resolve-versions
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
GRAFANA_IMAGE: ${{fromJson(needs.resolve-versions.outputs.matrix)}}
name: e2e ${{ matrix.GRAFANA_IMAGE.name }}@${{ matrix.GRAFANA_IMAGE.VERSION }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Install npm dependencies
run: npm ci
- name: Install Mage
uses: magefile/mage-action@v3
with:
install-only: true
- name: Build binaries
run: mage -v build:linux
- name: Build frontend
run: npm run build
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Start Grafana
run: |
docker compose pull
GRAFANA_VERSION=${{ matrix.GRAFANA_IMAGE.VERSION }} GRAFANA_IMAGE=${{ matrix.GRAFANA_IMAGE.NAME }} docker compose up -d
- name: Wait for grafana server
uses: grafana/plugin-actions/wait-for-grafana@main
- name: Run Playwright tests
id: run-tests
run: npx playwright test
```
--------------------------------
### E2E Tests CI Workflow with yarn
Source: https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/ci
This workflow configures E2E tests for a Grafana plugin using yarn for dependency management. It includes steps for checking out code, resolving Grafana versions, setting up Node.js, installing yarn dependencies, installing Mage, building binaries and frontend, installing Playwright browsers, starting Grafana, waiting for the server, and running Playwright tests.
```yaml
name: E2E tests
on:
pull_request:
schedule:
- cron: '0 11 * * *' #Run e2e tests once a day at 11 UTC
permissions:
contents: read
jobs:
resolve-versions:
name: Resolve Grafana images
runs-on: ubuntu-latest
timeout-minutes: 3
outputs:
matrix: ${{ steps.resolve-versions.outputs.matrix }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Resolve Grafana E2E versions
id: resolve-versions
uses: grafana/plugin-actions/e2e-version@main
playwright-tests:
needs: resolve-versions
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
GRAFANA_IMAGE: ${{fromJson(needs.resolve-versions.outputs.matrix)}}
name: e2e ${{ matrix.GRAFANA_IMAGE.name }}@${{ matrix.GRAFANA_IMAGE.VERSION }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Install yarn dependencies
run: yarn install
- name: Install Mage
uses: magefile/mage-action@v3
with:
install-only: true
- name: Build binaries
run: mage -v build:linux
- name: Build frontend
run: yarn build
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Start Grafana
run: |
docker compose pull
GRAFANA_VERSION=${{ matrix.GRAFANA_IMAGE.VERSION }} GRAFANA_IMAGE=${{ matrix.GRAFANA_IMAGE.NAME }} docker compose up -d
- name: Wait for grafana server
uses: grafana/plugin-actions/wait-for-grafana@main
- name: Run Playwright tests
id: run-tests
run: yarn playwright test
```
--------------------------------
### Implement Resource Handler with httpadapter
Source: https://grafana.com/developers/plugin-tools/how-to-guides/app-plugins/add-resource-handler
Demonstrates using the `httpadapter` package, `ServeMux`, and `http.Handler` to add support for retrieving namespaces and projects, and updating device state. This approach allows for handling multiple routes and HTTP methods.
```go
package myplugin
import (
"context"
"net/http"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
)
type MyPlugin struct {
resourceHandler backend.CallResourceHandler
}
func New() *MyPlugin {
p := &MyPlugin{}
mux := http.NewServeMux()
mux.HandleFunc("/namespaces", p.handleNamespaces)
mux.HandleFunc("/projects", p.handleProjects)
p.resourceHandler := httpadapter.New(mux)
return p
}
func (p *MyPlugin) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
return p.resourceHandler.CallResource(ctx, req, sender)
}
func (p *MyPlugin) handleNamespaces(rw http.ResponseWriter, req *http.Request) {
rw.Header().Add("Content-Type", "application/json")
_, err := rw.Write([]byte(`{ "namespaces": ["ns-1", "ns-2"] }`))
if err != nil {
return
}
rw.WriteHeader(http.StatusOK)
}
func (p *MyPlugin) handleProjects(rw http.ResponseWriter, req *http.Request) {
rw.Header().Add("Content-Type", "application/json")
_, err := rw.Write([]byte(`{ "projects": ["project-1", "project-2"] }`))
if err != nil {
return
}
rw.WriteHeader(http.StatusOK)
}
```
--------------------------------
### Use Basic App Fixture in a Test
Source: https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/test-an-app-plugin/app-pages
Import `test` and `expect` from your custom fixture to use the `gotoPage` function in your tests. This example verifies a welcome message on a start page.
```typescript
import { test, expect } from './fixtures.ts';
test('start page should welcome users to the app', async ({ gotoPage, page }) => {
await gotoPage('/start');
await expect(page.getByRole('heading', { name: 'Welcome to my app' })).toBeVisible();
});
```
--------------------------------
### E2E Test Workflow with npm
Source: https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/ci
This CI workflow configures E2E tests using npm for dependency management. It checks out code, resolves Grafana versions, sets up Node.js, installs npm dependencies, builds the frontend, installs Playwright browsers, starts Grafana, waits for it to be ready, and runs Playwright tests.
```yaml
name: E2E tests
on:
pull_request:
schedule:
- cron: '0 11 * * *' #Run e2e tests once a day at 11 UTC
permissions:
contents: read
jobs:
resolve-versions:
name: Resolve Grafana images
runs-on: ubuntu-latest
timeout-minutes: 3
outputs:
matrix: ${{ steps.resolve-versions.outputs.matrix }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Resolve Grafana E2E versions
id: resolve-versions
uses: grafana/plugin-actions/e2e-version@main
playwright-tests:
needs: resolve-versions
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
GRAFANA_IMAGE: ${{fromJson(needs.resolve-versions.outputs.matrix)}}
name: e2e ${{ matrix.GRAFANA_IMAGE.name }}@${{ matrix.GRAFANA_IMAGE.VERSION }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Install npm dependencies
run: npm ci
- name: Build frontend
run: npm run build
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Start Grafana
run: |
docker compose pull
GRAFANA_VERSION=${{ matrix.GRAFANA_IMAGE.VERSION }} GRAFANA_IMAGE=${{ matrix.GRAFANA_IMAGE.NAME }} docker compose up -d
- name: Wait for grafana server
uses: grafana/plugin-actions/wait-for-grafana@main
- name: Run Playwright tests
id: run-tests
run: npx playwright test
```
--------------------------------
### Initialize App with ServeMux
Source: https://grafana.com/developers/plugin-tools/how-to-guides/app-plugins/add-backend-component
Configure the CallResourceHandler using an httpadapter and ServeMux to manage resource routes.
```go
type App struct {
backend.CallResourceHandler
}
// NewApp creates a new example *App instance.
func NewApp(_ context.Context, _ backend.AppInstanceSettings) (instancemgmt.Instance, error) {
var app App
// Use an httpadapter (provided by the SDK) for resource calls. This allows us
// to use a *http.ServeMux for resource calls, so we can map multiple routes
// to CallResource without having to implement extra logic.
mux := http.NewServeMux()
app.registerRoutes(mux)
// implement the CallResourceHandler interface
app.CallResourceHandler = httpadapter.New(mux)
return &app, nil
}
```
--------------------------------
### E2E Test Workflow with yarn
Source: https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/ci
This CI workflow configures E2E tests using yarn for dependency management. It includes steps for checking out code, resolving Grafana versions, setting up Node.js, installing yarn dependencies, building the frontend, installing Playwright browsers, starting Grafana, waiting for it, and running Playwright tests.
```yaml
name: E2E tests
on:
pull_request:
schedule:
- cron: '0 11 * * *' #Run e2e tests once a day at 11 UTC
permissions:
contents: read
jobs:
resolve-versions:
name: Resolve Grafana images
runs-on: ubuntu-latest
timeout-minutes: 3
outputs:
matrix: ${{ steps.resolve-versions.outputs.matrix }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Resolve Grafana E2E versions
id: resolve-versions
uses: grafana/plugin-actions/e2e-version@main
playwright-tests:
needs: resolve-versions
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
GRAFANA_IMAGE: ${{fromJson(needs.resolve-versions.outputs.matrix)}}
name: e2e ${{ matrix.GRAFANA_IMAGE.name }}@${{ matrix.GRAFANA_IMAGE.VERSION }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Install yarn dependencies
run: yarn install
- name: Build frontend
run: yarn build
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Start Grafana
run: |
docker compose pull
GRAFANA_VERSION=${{ matrix.GRAFANA_IMAGE.VERSION }} GRAFANA_IMAGE=${{ matrix.GRAFANA_IMAGE.NAME }} docker compose up -d
- name: Wait for grafana server
uses: grafana/plugin-actions/wait-for-grafana@main
- name: Run Playwright tests
id: run-tests
run: yarn playwright test
```
--------------------------------
### E2E Test Workflow with pnpm
Source: https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/ci
This CI workflow configures E2E tests using pnpm for dependency management. It includes steps for checking out code, setting up pnpm, resolving Grafana versions, setting up Node.js, installing pnpm dependencies, building the frontend, installing Playwright browsers, starting Grafana, waiting for it, and running Playwright tests.
```yaml
name: E2E tests
on:
pull_request:
schedule:
- cron: '0 11 * * *' #Run e2e tests once a day at 11 UTC
permissions:
contents: read
jobs:
resolve-versions:
name: Resolve Grafana images
runs-on: ubuntu-latest
timeout-minutes: 3
outputs:
matrix: ${{ steps.resolve-versions.outputs.matrix }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Resolve Grafana E2E versions
id: resolve-versions
uses: grafana/plugin-actions/e2e-version@main
playwright-tests:
needs: resolve-versions
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
GRAFANA_IMAGE: ${{fromJson(needs.resolve-versions.outputs.matrix)}}
name: e2e ${{ matrix.GRAFANA_IMAGE.name }}@${{ matrix.GRAFANA_IMAGE.VERSION }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Install pnpm dependencies
run: pnpm install --frozen-lockfile
- name: Build frontend
run: pnpm run build
- name: Install Playwright Browsers
run: pnpm playwright install --with-deps
- name: Start Grafana
run: |
docker compose pull
GRAFANA_VERSION=${{ matrix.GRAFANA_IMAGE.VERSION }} GRAFANA_IMAGE=${{ matrix.GRAFANA_IMAGE.NAME }} docker compose up -d
- name: Wait for grafana server
uses: grafana/plugin-actions/wait-for-grafana@main
- name: Run Playwright tests
```
--------------------------------
### Scaffold a new app plugin
Source: https://grafana.com/developers/plugin-tools/how-to-guides/app-plugins/add-backend-component
Run the create-plugin tool to initialize a new project with backend support.
```shell
npx @grafana/create-plugin@latest
```
--------------------------------
### Build plugin backend
Source: https://grafana.com/developers/plugin-tools/how-to-guides/app-plugins/add-backend-component
Compile the Go backend binary using Mage.
```shell
mage -v build:linux
```
--------------------------------
### Install i18next-cli with Yarn
Source: https://grafana.com/developers/plugin-tools/how-to-guides/plugin-internationalization
Install the `i18next-cli` development dependency using Yarn.
```shell
yarn add --dev i18next-cli
```
--------------------------------
### Install @grafana/plugin-e2e
Source: https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/migrate-from-grafana-e2e
Commands to install the latest version of @grafana/plugin-e2e as a development dependency.
```shell
npm install @grafana/plugin-e2e@latest --save-dev
```
```shell
yarn add @grafana/plugin-e2e@latest --dev
```
```shell
pnpm add @grafana/plugin-e2e@latest --save-dev
```
--------------------------------
### Implement Logging with backend.Logger
Source: https://grafana.com/developers/plugin-tools/how-to-guides/data-source-plugins/add-logs-metrics-traces-for-backend-plugins
Demonstrates using the global logger to record events with various severity levels and key-value pairs.
```go
package plugin
import (
"errors"
"github.com/grafana/grafana-plugin-sdk-go/backend"
)
func main() {
backend.Logger.Debug("Debug msg", "someID", 1)
backend.Logger.Info("Info msg", "queryType", "default")
backend.Logger.Warning("Warning msg", "someKey", "someValue")
backend.Logger.Error("Error msg", "error", errors.New("An error occurred"))
}
```
--------------------------------
### Install Plugin Dependencies
Source: https://grafana.com/developers/plugin-tools/tutorials/build-an-app-plugin
Install all necessary npm packages for your Grafana plugin development.
```shell
npm install
```
--------------------------------
### Configure Global Tracer in Go
Source: https://grafana.com/developers/plugin-tools/how-to-guides/data-source-plugins/add-logs-metrics-traces-for-backend-plugins
Use datasource.Manage or app.Manage to initialize the global tracer with custom attributes during plugin startup.
```go
func main() {
if err := datasource.Manage("MY_PLUGIN_ID", plugin.NewDatasource, datasource.ManageOpts{
TracingOpts: tracing.Opts{
// Optional custom attributes attached to the tracer's resource.
// The tracer will already have some SDK and runtime ones pre-populated.
CustomAttributes: []attribute.KeyValue{
attribute.String("my_plugin.my_attribute", "custom value"),
},
},
}); err != nil {
log.DefaultLogger.Error(err.Error())
os.Exit(1)
}
}
```
--------------------------------
### Install Playwright Browsers
Source: https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/ci
Installs the necessary browser binaries for Playwright, including dependencies, to run end-to-end tests.
```yaml
run: pnpm playwright install --with-deps
```
--------------------------------
### Build Frontend Code for Production
Source: https://grafana.com/developers/plugin-tools
Use this command to create a production build of your plugin's frontend code, using npm.
```bash
npm run build
```
--------------------------------
### Build Frontend Code for Production with pnpm
Source: https://grafana.com/developers/plugin-tools
Use this command to create a production build of your plugin's frontend code, using pnpm.
```bash
pnpm run build
```
--------------------------------
### Install target plugin via environment variable
Source: https://grafana.com/developers/plugin-tools/how-to-guides/ui-extensions/local-development-setup
Use the GF_INSTALL_PLUGINS environment variable in docker-compose.yml to install a plugin from a remote URL.
```yaml
environment:
- GF_INSTALL_PLUGINS=https://example.com/path/to/your-plugin.zip;your-plugin-id
```
--------------------------------
### Initialize authenticated client in backend data source
Source: https://grafana.com/developers/plugin-tools/how-to-guides/data-source-plugins/convert-a-frontend-datasource-to-backend
Create and store an authenticated HTTP client in the constructor for reuse across requests.
```go
package plugin
import (
...
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
...
)
func NewDatasource(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
opts, err := settings.HTTPClientOptions(ctx)
if err != nil {
return nil, fmt.Errorf("http client options: %w", err)
}
opts.Header.Add("Authorization", "Bearer " + settings.DecryptedSecureJSONData["token"])
cli, err := httpclient.New(opts)
if err != nil {
return nil, fmt.Errorf("httpclient new: %w", err)
}
return &Datasource{
httpClient: cl,
}, nil
}
// In any other method
res, err := d.httpClient.Get("https://api.example.com/v1/users")
// Handle response
```
--------------------------------
### Migrate Plugin using npm
Source: https://grafana.com/developers/plugin-tools/migration-guides/migrate-from-toolkit
Run this command in the root directory of your existing plugin to start the migration process to create-plugin. Ensure your plugin code is backed up and on a clean Git branch.
```shell
npx @grafana/create-plugin@latest migrate
```
--------------------------------
### Set up MCP Provider in React Components
Source: https://grafana.com/developers/plugin-tools/how-to-guides/app-plugins/use-llms-and-mcp
This example shows how to use `mcp.MCPClientProvider` and `useMCPClient` hook for accessing the MCP client in React. It utilizes Suspense for loading states and ErrorBoundary for handling connection or configuration issues.
```tsx
import React, { Suspense } from 'react';
import { mcp } from '@grafana/llm';
import { ErrorBoundary, Spinner } from '@grafana/ui';
function App() {
return (
}>
{({ error }) => {
if (error) {
return Error with MCP: {error.message}
;
}
return (
);
}}
);
}
```
--------------------------------
### Example Grafana Plugin Request Metrics Output
Source: https://grafana.com/developers/plugin-tools/how-to-guides/data-source-plugins/add-logs-metrics-traces-for-backend-plugins
This is an example output of the `grafana_plugin_request_total` metric, which tracks plugin request success rates per endpoint, status, and status source. It is automatically collected by the SDK.
```shell
# HELP grafana_plugin_request_total The total amount of plugin requests
# TYPE grafana_plugin_request_total counter
grafana_plugin_request_total{endpoint="queryData",status="error",status_source="plugin"} 1
grafana_plugin_request_total{endpoint="queryData",status="ok",status_source="plugin"} 4
```
--------------------------------
### Start Grafana Instance
Source: https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/ci
Pulls the latest Docker images for Grafana and starts the Grafana service in detached mode using Docker Compose. It allows specifying Grafana version and image name via environment variables.
```yaml
run: |
docker compose pull
GRAFANA_VERSION=${{ matrix.GRAFANA_IMAGE.VERSION }} GRAFANA_IMAGE=${{ matrix.GRAFANA_IMAGE.NAME }} docker compose up -d
```
--------------------------------
### Handle Initial Migration with Empty Plugin Version
Source: https://grafana.com/developers/plugin-tools/how-to-guides/panel-plugins/migration-handler-for-panels
This snippet demonstrates how to handle the initial migration scenario where `pluginVersion` is empty, or when migrating from older versions starting with '1.'. It ensures options are set to a default 'compact' display mode.
```typescript
import { config } from '@grafana/runtime';
function migrationHandler(panel: PanelModel) {
const options = Object.assign({}, panel.options);
// pluginVersion will be empty
// if the plugin didn't implement a migration handler before
// or contain the version of the plugin when the panel was last saved after a migration handler was called.
const pluginVersion = panel?.pluginVersion ?? '';
if (pluginVersion === '' || pluginVersion.startsWith('1.')) {
options.displayMode = 'compact';
}
return options;
}
```
--------------------------------
### Build Frontend Code in Watch Mode with pnpm
Source: https://grafana.com/developers/plugin-tools
Run this command to build the plugin's frontend code and continuously monitor for changes during development, using pnpm.
```bash
pnpm run dev
```
--------------------------------
### E2E Tests CI Workflow with pnpm
Source: https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/ci
This workflow configures E2E tests for a Grafana plugin using pnpm for dependency management. It includes steps for checking out code, setting up pnpm, resolving Grafana versions, setting up Node.js, installing pnpm dependencies, installing Mage, and building binaries.
```yaml
name: E2E tests
on:
pull_request:
schedule:
- cron: '0 11 * * *' #Run e2e tests once a day at 11 UTC
permissions:
contents: read
jobs:
resolve-versions:
name: Resolve Grafana images
runs-on: ubuntu-latest
timeout-minutes: 3
outputs:
matrix: ${{ steps.resolve-versions.outputs.matrix }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Resolve Grafana E2E versions
id: resolve-versions
uses: grafana/plugin-actions/e2e-version@main
playwright-tests:
needs: resolve-versions
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
GRAFANA_IMAGE: ${{fromJson(needs.resolve-versions.outputs.matrix)}}
name: e2e ${{ matrix.GRAFANA_IMAGE.name }}@${{ matrix.GRAFANA_IMAGE.VERSION }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Install pnpm dependencies
run: pnpm install --frozen-lockfile
- name: Install Mage
uses: magefile/mage-action@v3
with:
install-only: true
- name: Build binaries
```
--------------------------------
### Bootstrap plugin with Go SDK
Source: https://grafana.com/developers/plugin-tools/migration-guides/update-from-grafana-versions/migrate-7_x-to-8_x
Updates the main package to use the new datasource.Manage function for plugin bootstrapping.
```go
// before
package main
import (
"github.com/grafana/grafana_plugin_model/go/datasource"
hclog "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin"
"github.com/myorgid/datasource/pkg/plugin"
)
func main() {
pluginLogger.Debug("Running GRPC server")
ds, err := NewSampleDatasource(pluginLogger);
if err != nil {
pluginLogger.Error("Unable to create plugin");
}
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "grafana_plugin_type",
MagicCookieValue: "datasource",
},
Plugins: map[string]plugin.Plugin{
"myorgid-datasource": &datasource.DatasourcePluginImpl{Plugin: ds},
},
GRPCServer: plugin.DefaultGRPCServer,
})
}
// after
package main
import (
"os"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
"github.com/myorgid/datasource/pkg/plugin"
)
func main() {
log.DefaultLogger.Debug("Running GRPC server")
if err := datasource.Manage("myorgid-datasource", NewSampleDatasource, datasource.ManageOpts{}); err != nil {
log.DefaultLogger.Error(err.Error())
os.Exit(1)
}
}
```
--------------------------------
### Scaffold a Data Source Plugin
Source: https://grafana.com/developers/plugin-tools/tutorials/build-ai-plugin
Use create-plugin to scaffold a new data source plugin with a backend component. This command initializes the plugin structure and necessary files.
```sh
npx @grafana/create-plugin@latest --plugin-type=datasource --backend --plugin-name=bcapi --org-name=myorg
```
--------------------------------
### Verify plugin registration
Source: https://grafana.com/developers/plugin-tools/how-to-guides/app-plugins/add-backend-component
Example log output confirming the plugin has been successfully registered by Grafana.
```text
INFO[01-01|12:00:00] Plugin registered logger=plugin.loader pluginID=
```
--------------------------------
### Scaffold plugin with CLI arguments
Source: https://grafana.com/developers/plugin-tools/reference/cli-commands
Bypass interactive prompts by providing configuration directly via command-line arguments.
```text
npx @grafana/create-plugin \
--plugin-type="app" \
--plugin-name="myPlugin" \
--org-name="myorg" \
--backend
```
--------------------------------
### Convert kbn.valueFormats to @grafana/data
Source: https://grafana.com/developers/plugin-tools/migration-guides/angular-react/angular-react-convert-from-kbn
This example shows how to format a value using the new `getValueFormat` and `formattedValueToString` methods from `@grafana/data`.
```typescript
const formatFunc = kbn.valueFormats[this.panel.format];
data.valueFormatted = formatFunc(data.value, decimalInfo.decimals, decimalInfo.scaledDecimals);
```
```typescript
import { formattedValueToString, getFieldDisplayName, getValueFormat, reduceField } from '@grafana/data';
const valueFields: Field[] = [];
for (const aField of frame.fields) {
if (aField.type === FieldType.number) {
valueFields.push(aField);
}
}
for (const valueField of valueFields) {
const standardCalcs = reduceField({ field: valueField!, reducers: ['bogus'] });
const result = getValueFormat(valueField!.config.unit)(operatorValue, maxDecimals, undefined, undefined);
const valueFormatted = formattedValueToString(result);
}
```
--------------------------------
### Build the plugin backend
Source: https://grafana.com/developers/plugin-tools/publish-a-plugin/package-a-plugin
Use the mage tool to build the backend component of the plugin.
```text
mage
```