Fabric Real-Time Dashboard (KQLDashboard)
Hand-authoring and Git-editing the RealTimeDashboard.json definition. The
portal load endpoint validates beyond JSON Schema conformance — most of what
breaks a hand-authored dashboard is in the wiring rules below, not the syntax.
Facts verified against a live dashboard (schema_version 81, Aug 2026) and
Real-time dashboard - Git integration.
File anatomy
Item folder: <Name>.KQLDashboard/ with .platform + RealTimeDashboard.json
(portal may name it RealTimeDashboard-N.json). Top-level keys:
schema_version, flavor ("RTDashboard_Regular"), autoRefresh, tiles,
baseQueries, parameters, dataSources, pages, queries, embeddedApps
(+ portal-managed $schema/id/eTag/title, which a Git-created shell may
omit — leave them however the portal wrote them).
All KQL text lives in queries[]; everything else points at it:
queries[] — {id, dataSource: {kind: "inline", dataSourceId}, text, usedVariables}
tiles[] — {id, queryRef: {kind: "query", queryId} | {kind: "baseQuery", baseQueryId}, title, visualType, pageId, layout, visualOptions}
baseQueries[] — {id, variableName, queryId}; the text is the referenced
query object. Variable names must start with _. A tile query that uses one
references the variable in its KQL text and lists it in usedVariables.
Base queries must each be a single tabular expression (they are wrapped as
let _name = (<text>);) — no scalar lambdas, no internal let statements.
parameters[] — kind: "duration" exposes two variables via
beginVariableName / endVariableName (commonly _startTime / _endTime).
dataSources[] — kind: "kusto-trident" with databaseArtifactId (the
KQLDatabase item id) for Fabric-native sources.
Load-time validation rules
- Every
queryId referenced exactly once, counted across
tiles[].queryRef.queryId, baseQueries[].queryId, and
parameters[].dataSource.queryRef.queryId. Duplicating a tile means
duplicating its query with a fresh id too.
- Every
id is an RFC-4122 UUID, unique within its category. Readable
pseudo-ids are rejected. For deterministic scripted edits use
uuid.uuid5(namespace, label).
- Misleading error:
/tiles/N/queryRef ... must have required property 'baseQueryId' almost always means a malformed queryRef.queryId — the
schema's oneOf fails the query branch and reports the baseQuery
branch's complaints instead. Fix the UUID and the cascade clears.
- Identity preservation: never change existing
ids (top-level, tile,
page, query, dataSource, parameter), pageId, queryRef.queryId,
dataSource.dataSourceId, parameter variableNames, or .platform
logicalId. A changed identifier is treated as delete + recreate on the
next Update from Git, losing pinned references and share targets.
Layout
24-column grid. layout: {x, y, width, height} in grid units; x + width <= 24. Typical: stat cards 4–6 wide × 4 tall, charts 8–12 wide × 7–9 tall.
Visuals
visualType is a free string in the schema — the UI interprets it. Working
values: card (single stat), multistat, bar, column, table, map,
kpi, markdownCard (plus line/area/pie/scatter/anomalychart/funnel/
heatmap/plotly). Options live in the flat visualOptions bag, prefixed per
visual (multiStat__labelColumn, map__latitudeColumn, kpi__valueColumn,
generic xColumn/yColumns/hideLegend/…). Minimal or empty
visualOptions: {} is valid — column inference handles the simple cases.
- multistat: rows = stats;
multiStat__labelColumn / multiStat__valueColumn,
multiStat__displayOrientation, multiStat__slot: {width, height} (inner
grid of stat slots).
- kpi (gauge/bar/donut/number via
kpi__visualType): kpi__minValue,
kpi__maxValue, and kpi__referenceLines take static numbers only — a
dynamic (query-computed) target cannot be bound. For "actual vs computed
target" use a multistat or fold the comparison into the query.
card and multistat render string values fine — the basis of the
display-edge formatting pattern below.
Formatting at the display edge
There are no per-tile number-format options (only the kpi visual has
kpi__valueFormat). Currency / percent presentation is done in KQL by
emitting formatted strings — see references/REFERENCE.md
for a validated money() lambda ("$1,234,567.89", rounding carry,
negatives). KQL has no format_number() and RE2 regex has no lookahead, so
comma grouping must be hand-rolled. Numeric axes (bar/column y-columns) must
stay numeric — put the unit in the tile title and column name
(['Order Value ($)']) instead. Related KQL pitfalls (round() returns
real, toint() nulls on decimal strings): rules/coding-kql.md.
Auto refresh
"autoRefresh": {"enabled": true, "defaultInterval": "5m", "minInterval": "30s"}
— intervals from the enum 1s|10s|30s|1m|5m|15m|30m|1h|2h|1d. minInterval
caps what viewers may select.
Live JSON Schema
https://dataexplorer.azure.com/static/d/schema/{schema_version}/dashboard.json
plus sibling tile.json, query.json, baseQuery.json, parameter.json.
The $schema URL embedded in Microsoft's own sample files
(pbiadx.powerbi.com/static/d/schema/...) returns 404 — swap the host.
What does not exist
- No image/render REST API. The Power BI
exportToFile API does not
cover KQL dashboards; the RTDB REST surface is item CRUD + the JSON
definition, and the portal's "export" is the JSON file. Visual verification
means a browser (screenshot or automation with an authenticated session);
data-level verification means running the tile queries directly against the
cluster.
- No cross-tile dynamic references — a tile query sees base queries and
parameter variables, nothing from other tiles.
Editing workflow
Round-trip options: Git sync (Update from Git), or portal Manage →
Replace with file for immediate feedback. Test tile queries before shipping
by composing them exactly as the dashboard does: prepend
let <variableName> = (<base query text>); for each entry in
usedVariables, then run against the database. Serialization is CRLF with
no final newline (JSON item part) — see rules/fabric-git-serialization.md.
1---2name: fabric-realtime-dashboard3description: Use for Microsoft Fabric Real-Time Dashboards (KQLDashboard item) — authoring or editing RealTimeDashboard.json by hand or via Git: file anatomy (queries[] holds all KQL text; tiles reference it by queryRef.queryId; baseQueries are {id, variableName, queryId} wired through usedVariables), load-time validation (every queryId referenced exactly once, RFC-4122 UUIDs, identity preservation — changing ids = delete+recreate on sync, misleading 'baseQueryId' error = malformed queryId), the 24-column tile grid, visual types (card, multistat, bar, column, table, map, kpi) and visualOptions, the kpi gauge's static-only min/max/reference lines, autoRefresh intervals, display-edge formatting in KQL (no per-tile number formats — emit currency/percent as strings), the live JSON Schema at dataexplorer.azure.com/static/d/schema/{v}/dashboard.json, and the missing image-export REST API. Invoke on mentions of Real-Time Dashboard, RTDB, KQL dashboard, dashboard tiles/base queries, or RealTimeDashboard.json.4---56# Fabric Real-Time Dashboard (KQLDashboard)78Hand-authoring and Git-editing the `RealTimeDashboard.json` definition. The9portal load endpoint validates beyond JSON Schema conformance — most of what10breaks a hand-authored dashboard is in the wiring rules below, not the syntax.11Facts verified against a live dashboard (schema_version 81, Aug 2026) and12[Real-time dashboard - Git integration](https://learn.microsoft.com/fabric/real-time-intelligence/git-real-time-dashboard).1314## File anatomy1516Item folder: `<Name>.KQLDashboard/` with `.platform` + `RealTimeDashboard.json`17(portal may name it `RealTimeDashboard-N.json`). Top-level keys:18`schema_version`, `flavor` (`"RTDashboard_Regular"`), `autoRefresh`, `tiles`,19`baseQueries`, `parameters`, `dataSources`, `pages`, `queries`, `embeddedApps`20(+ portal-managed `$schema`/`id`/`eTag`/`title`, which a Git-created shell may21omit — leave them however the portal wrote them).2223All KQL text lives in `queries[]`; everything else points at it:2425- `queries[]` — `{id, dataSource: {kind: "inline", dataSourceId}, text, usedVariables}`26- `tiles[]` — `{id, queryRef: {kind: "query", queryId} | {kind: "baseQuery", baseQueryId}, title, visualType, pageId, layout, visualOptions}`27- `baseQueries[]` — `{id, variableName, queryId}`; the text is the referenced28 query object. Variable names must start with `_`. A tile query that uses one29 references the variable in its KQL text **and** lists it in `usedVariables`.30 Base queries must each be a single tabular expression (they are wrapped as31 `let _name = (<text>);`) — no scalar lambdas, no internal `let` statements.32- `parameters[]` — `kind: "duration"` exposes two variables via33 `beginVariableName` / `endVariableName` (commonly `_startTime` / `_endTime`).34- `dataSources[]` — `kind: "kusto-trident"` with `databaseArtifactId` (the35 KQLDatabase item id) for Fabric-native sources.3637## Load-time validation rules3839- **Every `queryId` referenced exactly once**, counted across40 `tiles[].queryRef.queryId`, `baseQueries[].queryId`, and41 `parameters[].dataSource.queryRef.queryId`. Duplicating a tile means42 duplicating its query with a fresh id too.43- **Every `id` is an RFC-4122 UUID**, unique within its category. Readable44 pseudo-ids are rejected. For deterministic scripted edits use45 `uuid.uuid5(namespace, label)`.46- **Misleading error**: `/tiles/N/queryRef ... must have required property47 'baseQueryId'` almost always means a malformed `queryRef.queryId` — the48 schema's `oneOf` fails the `query` branch and reports the `baseQuery`49 branch's complaints instead. Fix the UUID and the cascade clears.50- **Identity preservation**: never change existing `id`s (top-level, tile,51 page, query, dataSource, parameter), `pageId`, `queryRef.queryId`,52 `dataSource.dataSourceId`, parameter `variableName`s, or `.platform`53 `logicalId`. A changed identifier is treated as delete + recreate on the54 next *Update from Git*, losing pinned references and share targets.5556## Layout575824-column grid. `layout: {x, y, width, height}` in grid units; `x + width <=5924`. Typical: stat cards 4–6 wide × 4 tall, charts 8–12 wide × 7–9 tall.6061## Visuals6263`visualType` is a free string in the schema — the UI interprets it. Working64values: `card` (single stat), `multistat`, `bar`, `column`, `table`, `map`,65`kpi`, `markdownCard` (plus line/area/pie/scatter/anomalychart/funnel/66heatmap/plotly). Options live in the flat `visualOptions` bag, prefixed per67visual (`multiStat__labelColumn`, `map__latitudeColumn`, `kpi__valueColumn`,68generic `xColumn`/`yColumns`/`hideLegend`/…). Minimal or empty69`visualOptions: {}` is valid — column inference handles the simple cases.7071- **multistat**: rows = stats; `multiStat__labelColumn` / `multiStat__valueColumn`,72 `multiStat__displayOrientation`, `multiStat__slot: {width, height}` (inner73 grid of stat slots).74- **kpi** (gauge/bar/donut/number via `kpi__visualType`): `kpi__minValue`,75 `kpi__maxValue`, and `kpi__referenceLines` take **static numbers only** — a76 dynamic (query-computed) target cannot be bound. For "actual vs computed77 target" use a multistat or fold the comparison into the query.78- `card` and `multistat` render **string** values fine — the basis of the79 display-edge formatting pattern below.8081## Formatting at the display edge8283There are no per-tile number-format options (only the kpi visual has84`kpi__valueFormat`). Currency / percent presentation is done in KQL by85emitting formatted strings — see [references/REFERENCE.md](references/REFERENCE.md)86for a validated `money()` lambda (`"$1,234,567.89"`, rounding carry,87negatives). KQL has no `format_number()` and RE2 regex has no lookahead, so88comma grouping must be hand-rolled. Numeric axes (bar/column y-columns) must89stay numeric — put the unit in the tile title and column name90(`['Order Value ($)']`) instead. Related KQL pitfalls (`round()` returns91`real`, `toint()` nulls on decimal strings): `rules/coding-kql.md`.9293## Auto refresh9495`"autoRefresh": {"enabled": true, "defaultInterval": "5m", "minInterval": "30s"}`96— intervals from the enum `1s|10s|30s|1m|5m|15m|30m|1h|2h|1d`. `minInterval`97caps what viewers may select.9899## Live JSON Schema100101`https://dataexplorer.azure.com/static/d/schema/{schema_version}/dashboard.json`102plus sibling `tile.json`, `query.json`, `baseQuery.json`, `parameter.json`.103The `$schema` URL embedded in Microsoft's own sample files104(`pbiadx.powerbi.com/static/d/schema/...`) returns 404 — swap the host.105106## What does not exist107108- **No image/render REST API.** The Power BI `exportToFile` API does not109 cover KQL dashboards; the RTDB REST surface is item CRUD + the JSON110 definition, and the portal's "export" is the JSON file. Visual verification111 means a browser (screenshot or automation with an authenticated session);112 data-level verification means running the tile queries directly against the113 cluster.114- No cross-tile dynamic references — a tile query sees base queries and115 parameter variables, nothing from other tiles.116117## Editing workflow118119Round-trip options: Git sync (*Update from Git*), or portal **Manage →120Replace with file** for immediate feedback. Test tile queries before shipping121by composing them exactly as the dashboard does: prepend122`let <variableName> = (<base query text>);` for each entry in123`usedVariables`, then run against the database. Serialization is CRLF with124**no final newline** (JSON item part) — see `rules/fabric-git-serialization.md`.