Files
superset2/docs/developer_docs/extensions/extension-points/dashboards.md
Claude Code fefe877cda feat(extensions): add dashboard renderer contribution point
Introduces a single-slot `dashboards` contribution point (SIP-151
architecture) that lets an extension replace Superset's built-in
dashboard renderer while the host keeps owning data fetching,
hydration, URL/permalink resolution, CSS injection, and theming.

- New `dashboards` namespace in @apache-superset/core defining the
  DashboardRenderer descriptor and the Redux-free DashboardRendererProps
  contract (dashboard identity/metadata/layout, charts, datasets,
  initial dataMask/tabs/anchor, uiConfig, reserved change callbacks).
- Host registry (DashboardRendererProviders) with chat-style singleton
  semantics: most recent registration wins, displaced providers are
  unregistered with a warning, disposal of a displaced provider is a
  no-op.
- DashboardRendererHost resolves the active provider via
  useSyncExternalStore (late registration swaps live), wraps custom
  renderers in an ErrorBoundary, and always falls back to the built-in
  renderer in edit mode or when the EnableExtensions flag is off.
- The built-in stack (DashboardContainer/DashboardBuilder + filter
  selectors) moves behind the same contract as DefaultDashboardRenderer,
  preserving the lazy DashboardBuilder chunk.
- DashboardPage now builds the contract props from data it already
  fetches and renders DashboardRendererHost; hydration and all other
  host behavior are unchanged, so the embedded path inherits the seam.

This is the first step toward a fully Redux-decoupled dashboard
renderer and an Embedded SDK that is a thin wrapper over this
extension point.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 15:45:14 -07:00

6.3 KiB

title, sidebar_position
title sidebar_position
Dashboards 4

Dashboard Renderer Contributions

Extensions can replace Superset's built-in dashboard renderer with a custom implementation. This allows dashboards to be displayed in entirely different ways — kiosk layouts, alternative grid engines, story-style presentations — while reusing Superset's data fetching, authentication, theming, and URL/permalink handling.

Overview

The dashboard renderer is a single-slot contribution point:

  • At most one custom renderer is active at a time. The most recently registered renderer wins; a previously registered renderer is displaced and unregistered with a console warning.
  • When no custom renderer is registered, the host renders the built-in dashboard renderer.
  • Custom renderers handle view mode only. When a dashboard enters edit mode, the host always renders the built-in renderer (which owns drag-and-drop editing, undo/redo, and the component pane), returning to the custom renderer when edit mode exits.
  • A custom renderer that throws is contained by an error boundary; the host does not fall back to the built-in renderer on error.

The host keeps its behavior identical regardless of which renderer is active: it fetches the dashboard, charts, and datasets, resolves initial filter state from the URL (permalinks, native_filters_key, legacy filter params), injects dashboard CSS, and manages the document title. The renderer receives the results as props.

The Props Contract

Your renderer component receives DashboardRendererProps from @apache-superset/core/dashboards:

Prop Type Description
dashboard DashboardInfo Identity and parsed metadata: id, uuid, slug, title, css, metadata (parsed json_metadata), layout (parsed position_json), isPublished, isManagedExternally
charts DashboardChart[] Chart (slice) definitions as returned by GET /api/v1/dashboard/{id}/charts
datasets DashboardDataset[] Datasets as returned by GET /api/v1/dashboard/{id}/datasets
initialDataMask DashboardDataMask Initial filter state resolved by the host from the URL
initialActiveTabs string[]? Layout component ids of the initially active tabs (from permalink)
initialAnchor string? Layout component id to scroll to on mount (permalink anchor)
uiConfig DashboardUiConfig? Chrome-hiding flags (hideTitle, hideTab, hideChartControls, emitDataMasks), mirroring the embedded SDK's uiConfig
onDataMaskChange callback? Reserved — not supplied by the host yet
onActiveTabsChange callback? Reserved — not supplied by the host yet

The contract is designed to be Redux-free: everything a renderer needs to display a dashboard arrives via props, and host services are available through the public window.superset namespaces (authentication, navigation, theme, translation, and so on).

Renderer responsibilities

  • Chart data fetching: the host does not fetch chart data. Query for it yourself (e.g. POST /api/v1/chart/data with query contexts built from each chart's form_data).
  • Filter orchestration: applying initialDataMask, reacting to filter interactions, and refreshing affected charts are the renderer's responsibility.
  • Layout interpretation: dashboard.layout is the parsed position_json component tree (rows, columns, tabs, charts, markdown); interpret as much or as little of it as your presentation needs.

Theming works out of the box: renderers are mounted inside the host's theme providers, so useTheme from @apache-superset/core/theme reflects the dashboard's active theme.

Registering a Renderer

Register the renderer as a module-level side effect in your extension's entry point:

import { dashboards } from '@apache-superset/core';
import type { ComponentType } from 'react';

const KioskDashboardRenderer: ComponentType<
  dashboards.DashboardRendererProps
> = ({ dashboard, charts, initialDataMask }) => (
  <main>
    <h1>{dashboard.title}</h1>
    {/* render charts from `charts` + `dashboard.layout` */}
  </main>
);

dashboards.registerDashboardRenderer(
  { id: 'acme.kiosk-dashboard', name: 'Kiosk Dashboard Renderer' },
  KioskDashboardRenderer,
);

registerDashboardRenderer returns a Disposable. Disposing it removes your renderer if it is still the active one; disposing after being displaced by a newer registration is a no-op.

You can observe slot changes with dashboards.onDidRegisterDashboardRenderer and dashboards.onDidUnregisterDashboardRenderer, and inspect the active provider with dashboards.getDashboardRenderer().

Manifest Declaration

Declare the renderer in your extension's Contributions metadata (at most one per extension):

{
  "dashboardRenderer": {
    "id": "acme.kiosk-dashboard",
    "name": "Kiosk Dashboard Renderer",
    "description": "Full-screen kiosk presentation of dashboards"
  }
}

Current Limitations

  • Extensions load asynchronously after startup, so a dashboard opened before your extension finishes loading renders with the built-in renderer first and swaps to yours when registration lands.
  • onDataMaskChange and onActiveTabsChange are defined in the contract but not consumed by the host yet — filter state changed inside a custom renderer does not persist to permalinks.
  • While a custom renderer is active the host still hydrates its internal dashboard state so permalinks and embedded behavior remain intact; this is transparent to renderers but means the built-in state bookkeeping still runs.