databricks-aibi-custom-pages-react
Build full-page, high-fidelity React "custom pages" inside a Databricks AI/BI (Lakeview) dashboard — the mechanism behind pixel-perfect, custom-app-like dashboards (composite KPI cards, in-cell sparkl
SKILL.mddata_product_accelerator/skills/monitoring/06-databricks-aibi-custom-pages-react/SKILL.md
GitHubAI/BI Custom Pages (React)
⚠️ Status: undocumented — read before use
This skill documents a mechanism (<!-- @custom-page --> + viz.useCustomPageQuery) that is not
published in the official Databricks documentation as of 2026-09-21. The contract below was
reverse-engineered from a real, deployed .lvdash.json — it is accurate to that exemplar but is
not an officially supported API. Consequences:
- It may be a private/early preview; availability is not guaranteed in your workspace.
- The envelope keys, the
vizruntime, and the hook shape can change without notice. - Treat as
volatility: high. Verify it renders in your target workspace before building on it. - Prefer the documented tiers when they suffice: built-in widgets (
02) → Vega-Lite custom viz (05) → this skill only for full-page bespoke layouts.
If you need a fully supported, custom React surface, a Databricks App (see apps_lakebase/ and the
databricks-apps skill) is the documented product — it is a separate app, not an embedded dashboard page.
Fidelity Decision Gate (read first)
Pick the lowest tier that meets the requirement. Escalating for looks alone is a defect.
| Need | Use | Skill |
|---|---|---|
| Standard chart / KPI / table / filter | Built-in widget | 02-databricks-aibi-dashboards |
| A custom single chart the built-ins lack, with native cross-filter | Vega-Lite custom viz | 05-databricks-aibi-custom-viz-vega |
| A full-page bespoke layout — composite multi-metric cards, in-cell sparklines, custom filter chips, status pills, narrative/email surfaces — that no single chart can express | React custom page | this skill |
Output a Fidelity Decision Record before authoring, listing which triggers fired, e.g.:
Fidelity: React custom page — exec narrative surface; composite cards + inline sparklines + custom chips; built-ins and Vega single-chart both insufficient. Accepting: undocumented mechanism, no native cross-filter, JS maintenance, workspace-preview-only validation.
Tradeoffs (accept these explicitly)
- ❌ No native cross-filter / drill-through — you hand-code interactions inside React.
- ❌ No native export / accessibility guarantees from the platform chrome.
- ❌ Not headless-validatable — arbitrary React only truly verifies in a workspace preview.
- ❌ Higher maintenance — a large JS blob inside JSON; harder to review than declarative widgets.
- ✅ Total layout control — anything React can render (cards, grids, sparklines, chips, theming).
Architecture
Each canvas page hosts exactly one custom-page widget that owns the entire page layout.
The widget is a
multilineTextboxSpecwhose joinedlinesare:<!-- @custom-page --> { "code": "<CommonJS React source>", "manifest": {}, "datasetMap": [ { "alias": "...", "datasetId": "..." } ] }Standard dashboard machinery is unchanged and reused from
02: thedatasets[](withqueryLines), the Global Filters page, parameters,uiSettings.theme, and deployment. Only the content pages are React.A single dashboard may interleave standard pages (e.g. a
PAGE_TYPE_GLOBAL_FILTERSpage with built-in filters) and React custom pages — this is the recommended hybrid shape.
See references/custom-page-envelope.md for the exact envelope
and the double-escaping rules.
Authoring the React
- CommonJS, not ESM, no JSX. The exemplar uses:
Author withvar R = require('react'); function App(props) { /* ... */ return R.createElement(...); } module.exports.default = App;R.createElement(tag, props, ...children). (Whether the runtime accepts JSX/imports is unverified — the observed, working form isrequire/createElement/module.exports.default.) vizis an injected runtime global (onlyreactisrequired). Do not try torequire('viz').- Props provided to
Appand passed down:props.theme('dark'|'light'),props.height, and layout hints likeprops.flex. Branch all colors onprops.theme === 'dark'.
Data binding — viz.useCustomPageQuery
Read a dataset (by its alias from datasetMap) with the injected hook:
var res = viz.useCustomPageQuery({
query: {
datasetName: 'commit', // alias from datasetMap, NOT the datasetId
fields: [
{ fieldName: 'account_name', expression: '`account_name`' },
{ fieldName: 'pace_actual', expression: '`pace_actual`' }
]
}
});
// Return shape: { rows: [ { account_name: '...', pace_actual: 123 }, ... ] }
if (res && res.rows && res.rows.length) {
var first = res.rows[0];
// ...render...
}
datasetNameis the alias declared indatasetMap; the alias resolves to a dashboarddatasetId(which equals a dataset'snameindatasets[]).fields[].fieldNameis the key you read on each row;expressionis the backtick-quoted column.- Always guard on
res && res.rows && res.rows.lengthand render a loading/empty state otherwise (the exemplar renders skeleton shimmer tiles while data resolves).
Theming & loading
- Derive a palette from
props.theme; keep small helpers (toneFg/toneBg, currency/percent formatters) at module scope. Seereferences/authoring-patterns.md. - Provide a skeleton/loading component (inject a keyframe once into
document.head) so the page never flashes empty. - Optional
data-cp-element-idattributes on elements let the visual editor track them; they are not required for rendering.
Deployment & the escaping trap
A custom page is a normal dashboard widget, so deploy exactly as in 02 (base64 Workspace Import or
bundle deploy). The one hazard is triple nesting:
codeis a JS string inside- the
{code, manifest, datasetMap}JSON string inside - the
.lvdash.jsonmultilineTextboxSpec.linesarray.
Build the envelope programmatically (json.dumps) rather than by hand — never hand-escape the code
string. See references/custom-page-envelope.md for a builder.
Validation
scripts/validate_custom_page.py <dashboard.lvdash.json>checks: each custom page's envelope parses; everydatasetNameused inviz.useCustomPageQuery(...)resolves viadatasetMapto a real dataset indatasets[];module.exports.default(ormodule.exports) is present; and the content survives a base64 round-trip.- It cannot verify React correctness. Always preview in the workspace before shipping.
Templates
assets/templates/custom-page-single-metric.lvdash.json— minimal 1-dataset page: one dataset, one custom page reading it, theme-aware.assets/templates/custom-page-composite.lvdash.json— sanitized composite: KPI card row + a status list, driven by one dataset, with skeleton loading and light/dark theming. De-identified (generic${catalog}.${schema}sources; no internal names).
Related skills
monitoring/02-databricks-aibi-dashboards— built-in widgets, datasets, filters, deployment (reused).monitoring/05-databricks-aibi-custom-viz-vega— documented single-chart custom viz (prefer when a single chart suffices).apps_lakebase/+databricks-apps— the documented product for a full custom React app (separate from a dashboard).