/grafana-scenes
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
$ npx -y skills add grafana/skills --skill grafana-scenes --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/grafana-scenes
Context preview
The summary Claude sees to decide when to auto-load this skill.
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
SKILL.md
grafana-scenes.SKILL.mdname: 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
// 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
// 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)
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:
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:
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/)
Read more
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
// 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
// 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)
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:
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:
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/)
Public skills for working with Grafana, Prometheus, Loki, Tempo, Pyroscope, k6, and the broader LGTM observability stack. Compatible with Claude Code, Cursor, Codex, and any tool supporting the Agent Skills open standard.
Repo: grafana/skills
Other skills on grafana-skills.
- /admission-control
Use when the user asks to "write a validator", "add validation", "implement admission control", "write a mutating webhook", "add a mutation handler", "validate incoming resources", "implement admission logic", "add admission webhooks", "write ingress validation", or asks how to
Open skill - /app-sdk-concepts
Use when starting any grafana-app-sdk work — scaffolding a Grafana app, initializing a Grafana App Platform app, picking a deployment mode (standalone operator / grafana/apps / frontend-only), wiring app-specific config, or onboarding to the SDK. Covers `grafana-app-sdk` CLI
Open skill - /cue-kind-definition
Author CUE kind definitions for grafana-app-sdk apps - schemas, versioning, field constraints, named type definitions, custom routes, and codegen configuration. Scaffolds kinds via `grafana-app-sdk project kind add`, writes spec/status schemas with type constraints (regex, enum,
Open skill - /reconciler-logic
Implement reconcilers and watchers for grafana-app-sdk apps — write `TypedReconciler[*MyKind]` reconcile functions, apply generation-based skip patterns, do conflict-safe status updates via `resource.UpdateObject`, configure `BasicReconcileOptions` (namespace, label/field
Open skill - /adaptive-metrics
Cut Grafana Cloud Metrics cost by shrinking active-series count with Adaptive Metrics aggregation rules — auto-recommendations from query history, custom exact/regex rules, label-drop config, unused-metric detection, and Alloy remote_write fallback. Use when investigating a high
Open skill - /admin
Manage Grafana Cloud accounts — organizations, stacks, RBAC roles and assignments, SSO/SAML/OAuth/GitHub auth, service accounts for CI/CD, user invites, team membership, and API-driven provisioning. Creates stacks via the Cloud API, mints service-account tokens, applies role
Open skill

