scenes
A React/TypeScript framework for building interactive, dashboard-like Grafana app plugins around a reactive scene graph.
Repository Health
Technical Analysis
Grafana Scenes is the framework the Grafana team built to let app plugin authors construct dashboard-like experiences without hand-rolling panel rendering, query orchestration, or variable interpolation from scratch. At its core is a scene graph: every visual and logical piece of a dashboard (a panel, a layout, a time range, a variable, a data query) is a SceneObject with its own state, lifecycle (activate/deactivate), and event bus, composed into a tree that a React renderer walks to produce the UI.
On top of that primitive, Scenes ships batteries: SceneQueryRunner and SceneDataTransformer for running and transforming Grafana datasource queries, a full template-variable system (query, custom, ad-hoc filter, group-by, interval, constant, textbox, and data-source variables) with dependency tracking and macro interpolation, flexible grid/flex/split layouts, URL state sync, and SceneApp/SceneAppPage for defining multi-page, routed plugin apps. It is maintained by Grafana Labs and is the same engine that powers Explore Metrics, Explore Traces, and other Grafana Cloud “drilldown” apps, so it tracks Grafana core APIs (@grafana/data, @grafana/runtime, @grafana/ui) closely and requires Grafana 11.6+ as a peer.
Because it owns application structure — page routing, activation lifecycle, URL-synced state — rather than just rendering a widget, Scenes behaves more like an app framework than a component library: you build your plugin’s screens as scene graphs and let the framework manage data flow, variable interpolation, and time-range propagation for you.
What You Get
- A
SceneObjectBaseprimitive with typed state, an activation/deactivation lifecycle, and an event bus that every layout, panel, and data object in the tree builds on SceneQueryRunnerandSceneDataTransformerfor running Grafana datasource queries and transformation pipelines against any scene’s time range and variables- A full template-variable system — query, custom, ad-hoc filter, group-by, interval, constant, textbox, and data-source variables — with automatic dependency tracking and macro interpolation
- Layout primitives (flex, CSS grid, split panes, repeaters) for composing panels and controls into dashboard-like grids
SceneApp/SceneAppPagefor defining multi-page, routed plugin applications with URL-synced state out of the boxPanelBuildersandVizPanelBuilderfluent APIs for configuring Grafana visualization panels without hand-assembling field-config objects
Common Use Cases
- Building a Grafana app plugin that needs a dashboard-like, multi-panel exploration UI (e.g. Explore Metrics/Traces-style drilldown apps)
- Embedding an interactive, filterable data view inside a plugin page with synced time range and template variables
- Composing a custom multi-page plugin app with URL-addressable, bookmarkable state per page
- Building panels that react to shared variables (ad-hoc filters, group-by, query variables) across a whole scene without wiring subscriptions by hand
Under The Hood
Architecture
The framework centers on SceneObjectBase (packages/scenes/src/core/SceneObjectBase.tsx), an abstract class holding frozen, typed state plus an activation lifecycle (_isActive, activation/deactivation handler queues) and an EventBusSrv for pub/sub; every concrete object (layouts, panels, query runners, variables) extends it and is composed into a tree via a parent pointer set on construction, with sceneGraph (core/sceneGraph) providing tree-walk utilities like getClosest and interpolate for resolving values from the nearest ancestor. Data flows top-down: a SceneTimeRange and variable set live near the tree root, SceneQueryRunner/SceneDataTransformer (querying/) walk up via sceneGraph to find the time range and interpolate variables into queries, then push results down through RxJS streams (ReplaySubject, combineLatest) that panels subscribe to. Higher-level composition (SceneApp/SceneAppPage in components/SceneApp) layers routing and page lifecycle on top of the same object tree, so changing the core SceneObjectBase contract would ripple through every layout, panel, and data primitive in the package.
Tech Stack
TypeScript throughout, targeting React 18 as a peer dependency and built with Rollup (rollup.config.ts, esbuild plugin) into CJS and ESM bundles plus type declarations, orchestrated across the monorepo’s three packages (scenes, scenes-react, scenes-app) via Turborepo and Yarn workspaces. Runtime dependencies are RxJS for the reactive data layer, Lodash for utilities, @floating-ui/react, react-grid-layout and react-virtualized-auto-sizer for layout/virtualization, and react-select/@tanstack/react-virtual for variable pickers; the actual Grafana integration points (@grafana/data, @grafana/runtime, @grafana/ui, @grafana/schema) are peer dependencies resolved against the host Grafana instance rather than bundled.
Code Quality
The scenes package carries 86 test files (Jest + @swc/jest, React Testing Library, jest-canvas-mock for canvas-dependent panels) covering core primitives, query runners, variables, and components, and CI (node-ci.yml) runs ESLint, Prettier, full typecheck, and build on every change — there is no evidence of tests being skipped or CI-only enforcement bypassed. Code is strongly typed with explicit exported interfaces per module (types.ts files throughout), consistent PascalCase for classes/components and camelCase for functions, and errors are generally surfaced through typed RxJS error channels (toDataQueryError) rather than silently swallowed, though a couple of @ts-ignore escapes exist around fast-moving @grafana/runtime APIs.
What Makes It Unique Rather than exposing a fixed dashboard widget, Scenes exposes the same scene-graph abstraction Grafana core itself now uses internally for its “drilldown” apps (Explore Metrics, Explore Traces) — giving plugin authors dependency-tracked variables, activation-aware lifecycle, and URL-synced page state as composable primitives instead of a closed dashboard renderer. That lets a plugin build genuinely custom, app-like data experiences on Grafana’s own query/transform/variable engine instead of reimplementing it or being boxed into the standard dashboard JSON model.