Skip to main content
skill CX Codex

grafana-scenes — Claude Code skill: Grafana plugin pages

A skill for building Grafana plugin pages with the @grafana/scenes framework: panels, variables, drilldown navigation.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add grafana/skills --skill grafana-scenes -a claude-code
Open on GitHub

What grafana-scenes does

This skill covers @grafana/scenes, the declarative framework for Grafana plugin pages. A scene is a tree of objects: SceneApp → SceneAppPage → EmbeddedScene → layouts → panels. Each node can own data ($data), variables ($variables), a time range ($timeRange) and behaviors ($behaviors) that propagate down the tree.

Inside are working snippets: a scene file with SceneQueryRunner and QueryVariable, page registration through SceneAppPage, click-through drilldowns, tabs inside a detail view, transforms via SceneDataTransformer, custom objects on SceneObjectBase, and table column overrides. Panels come from chained builders — PanelBuilders.table(), .timeseries(), .stat(), .gauge(), .barchart() — with .setData(), .setTitle(), .setUnit() and .build().

A pitfalls section is included too. Pages with drilldowns or tabs need a wildcard in routePath, or navigation breaks. URL params go through encodeURIComponent/decodeURIComponent because K8s names contain /. A $varName in a query must exist in a SceneVariableSet higher up the tree. For instant queries, set both instant: true and format: 'table'.

What it helps with

  • build a Grafana plugin page with @grafana/scenes
  • add a table, timeseries or stat panel to a scene
  • wire a drilldown that opens on row click
  • define a cluster variable and use it in a query
  • extend SceneObjectBase with a custom interactive widget

Example prompts for the agent

  • Create a scene file for the plugin page with a table over the up metric
  • Add a drilldown from the table to a detail page per cluster
  • Define a multi-select QueryVariable cluster and use it in the query

How to install grafana-scenes

1Skills CLI (Vercel)

npx skills add grafana/skills --skill grafana-scenes -a claude-code

2Manually: put the files in place

~/.claude/skills/grafana-scenes/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/grafana-scenes/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about grafana-scenes

How do I install grafana-scenes in Claude?

Run npx skills add grafana/skills --skill grafana-scenes -a claude-code. The skill lands in ~/.claude/skills/grafana-scenes/SKILL.md.

What does the grafana-scenes skill do?

It guides the agent through writing Grafana plugin pages on @grafana/scenes: panels, variables, query runners, drilldowns and tabs.

Which mistakes does the skill cover?

For example a missing wildcard in routePath on pages with drilldowns, and query variables that have no SceneVariableSet ancestor.

Source text

SKILL.md · Apache-2.0
---
name: grafana-scenes
license: Apache-2.0
description: Build Grafana plugin pages using the @grafana/scenes framework. Use when creating new scene pages, adding panels/visualizations, setting up drilldown navigation, defining variables, configuring query runners, building table/timeseries/stat panels, or extending SceneObjectBase for custom scene objects. Triggers on any work involving SceneApp, SceneAppPage, EmbeddedScene, SceneQueryRunner, SceneDataTransformer, PanelBuilders, SceneFlexLayout, QueryVariable, or drilldown/tab configuration in Grafana plugins.
---

# @grafana/scenes Framework

Build reactive, data-driven Grafana plugin pages with declarative scene objects.

## Core Concepts

Scenes composes a tree of objects: `SceneApp` → `SceneAppPage` → `EmbeddedScene` → layouts → panels. Each node can own data (`$data`), variables (`$variables`), time ranges (`$timeRange`), and behaviors (`$behaviors`) that propagate down the tree.

## Quick Start: New Scene Page

### 1. Create the scene file

```typescript
// src/components/scenes/MyFeature/scene.tsx
import {
  EmbeddedScene, SceneFlexLayout, SceneFlexItem,
  SceneQueryRunner, SceneVariableSet, QueryVariable,
  PanelBuilders, VariableValueSelectors, SceneControlsSpacer,
} from '@grafana/scenes';

export function getMyFeatureScene(params: { datasource: DataSourceRef }) {
  const queryRunner = new SceneQueryRunner({
    datasource: params.datasource,
    queries: [{ refId: 'A', expr: 'up{cluster=~"$cluster"}', instant: true, format: 'table' }],
  });

  const panel = PanelBuilders.table()
    .setData(queryRunner)
    .setTitle('My Table')
    .build();

  return new EmbeddedScene({
    $variables: new SceneVariableSet({
      variables: [
        new QueryVariable({
          name: 'cluster',
          query: 'label_values(up, cluster)',
          datasource: params.datasource,
          isMulti: true, includeAll: true, defaultToAll: true,
        }),
      ],
    }),
    controls: [new VariableValueSelectors({}), new SceneControlsSpacer()],
    body: new SceneFlexLayout({
      direction: 'column',
      children: [new SceneFlexItem({ body: panel })],
    }),
  });
}
```

### 2. Create the page

```typescript
// src/components/scenes/MyFeature/MyFeature.tsx
import { SceneAppPage, SceneTimeRange } from '@grafana/scenes';

export function getMyFeaturePage(params) {
  return new SceneAppPage({
    title: 'My Feature',
    url: '/a/my-plugin-id/my-feature',
    routePath: 'my-feature/*',
    $timeRange: new SceneTimeRange({ from: 'now-1h', to: 'now' }),
    getScene: () => getMyFeatureScene(params),
    drilldowns: [],
  });
}
```

### 3. Register in SceneApp

Add the page to the `SceneApp` pages array in the root scene file.

## Key Patterns

### Drilldowns (click-through navigation)

```typescript
drilldowns: [{
  routePath: ':cluster/*',
  getPage: (match, parent) => new SceneAppPage({
    title: decodeURIComponent(match.params.cluster),
    url: `${parent.state.url}/${match.params.cluster}`,
    routePath: `${match.params.cluster}/*`,
    getScene: () => detailScene(decodeURIComponent(match.params.cluster)),
  }),
}]
```

### Tabs (sub-pages within a detail view)

Pass `tabs: [SceneAppPage, ...]` instead of `getScene` on a `SceneAppPage`. Each tab is itself a `SceneAppPage` with its own scene.

### Query with transformations

Wrap a `SceneQueryRunner` in `SceneDataTransformer` to apply Grafana transforms or custom RxJS operators:

```typescript
new SceneDataTransformer({
  $data: queryRunner,
  transformations: [
    { id: 'organize', options: { renameByName: { 'Value #A': 'CPU' } } },
    (ctx) => (source) => source.pipe(map((frames) => /* custom transform */)),
  ],
})
```

### Custom scene object

Extend `SceneObjectBase` with a static `Component` for custom interactive UI:

```typescript
class MyWidget extends SceneObjectBase<MyWidgetState> {
  static Component = ({ model }: SceneComponentProps<MyWidget>) => {
    const state = model.useState();
    return <div>{state.value}</div>;
  };
}
```

### Table column overrides

Build `ConfigOverrideRule` objects for drill-down links, filtering, units, widths, custom cells.

### Panel types

`PanelBuilders.table()`, `.timeseries()`, `.stat()`, `.gauge()`, `.barchart()` — chain `.setData()`, `.setTitle()`, `.setUnit()`, `.setOption()`, `.setOverrides()`, then `.build()`.

## Common Pitfalls

- Always use `routePath: 'path/*'` (with wildcard) on pages that have drilldowns or tabs
- `encodeURIComponent`/`decodeURIComponent` URL params — K8s names can contain `/`
- Variables referenced in queries as `$varName` must exist in an ancestor `SceneVariableSet`
- `getScene` is called lazily; don't create side effects in the factory
- For instant queries, set both `instant: true` and `format: 'table'`

## Resources

- [@grafana/scenes Documentation](https://grafana.com/docs/grafana/latest/developers/plugins/create-plugin-ui/grafana-scenes/)
- [@grafana/scenes npm](https://www.npmjs.com/package/@grafana/scenes)
- [Grafana Plugin Tools](https://grafana.com/developers/plugin-tools/)

© grafana · Apache-2.0 · grafana/skills

Similar skills

Everything for Frontend and UI →

From the same set47

ml-ai Enables machine learning and AI features in Grafana Cloud. It helps set up Prophet forecasts, DBSCAN outlier alerts, and Sift root-cause investigations. CX Codex +1 4,100installs admin A skill for Grafana Cloud account work: creates stacks, mints service-account tokens, configures SSO and RBAC roles. CX Codex +1 4,200installs plugin-bundle-size Helps reduce the module.js entry point in Grafana app plugins using React.lazy, Suspense, and Webpack code splitting. CX Codex +1 4,200installs admission-control Guides you through writing validators and mutators for grafana-app-sdk apps, so resources get checked and adjusted before they hit Kubernetes. CX Codex +1 4,200installs your-skill-name A starter skill template by Grafana for drafting agent instructions, structuring PromQL examples, and adhering to Agent Skills specifications. CX Codex +1 3,800installs prometheus-cardinality-troubleshooter Diagnoses active Prometheus cardinality incidents, including memory growth, slow queries, ingest errors, series churn and billing spikes. CX Codex +1 3,800installs skill-authoring Walks you through writing and reviewing SKILL.md files against the four-dimension rubric the grafana/skills CI gate enforces. Useful when a skill won't trigger or keeps failing review. CX Codex +1 3,800installs prometheus-label-strategy Audits and designs Prometheus label schemas to prevent high cardinality at the source, from application code to scrape targets, before series reach storage. CX Codex +1 3,800installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor