### 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 ```