# Power BI Custom Visuals Development

> This skill should be used when the user asks about developing, building, debugging, packaging, or certifying a custom Power BI visual with the pbiviz toolchain (powerbi-visuals-tools). Covers environment setup, the visual project structure, capabilities.json (data roles, dataView mappings, objects, features, privileges), the IVisual API lifecycle (constructor, update, getFormattingModel, destroy), reading the dataView (categorical, table, matrix, single), the modern format pane and formatting model utils, selection and cross-filtering, tooltips, context menus, drill-down, bookmarks, landing pages, rendering events, local storage, launchUrl, D3 rendering, unit testing, ESLint, packaging to a .pbiviz file, and submitting to AppSource / Partner Center for certification. Example user requests: "create a custom Power BI visual", "build a bar chart visual with pbiviz", "add a format pane card to my visual", "make my visual cross-filter other visuals", "why is my visual data view empty", "get my Power BI visual cert

- Skill: `thelobbi/power-bi-custom-visuals-development` (Agent Skill, multi-file: 19 files)
- Install (CLI): `npx skillmds@latest add thelobbi/power-bi-custom-visuals-development`
- Raw SKILL.md: https://api.skillmd.com/api/skills/thelobbi/power-bi-custom-visuals-development/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: thelobbi (https://skillmd.com/u/thelobbi)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/thelobbi/power-bi-custom-visuals-development

---


# Power BI Custom Visuals Development

Custom Power BI visuals are web components — written in TypeScript, rendered with D3/SVG/Canvas/HTML, and packaged as a single `.pbiviz` file — that run sandboxed inside an `iframe` in Power BI Desktop and the Power BI service. You build them with the **pbiviz** command-line tool (the `powerbi-visuals-tools` npm package) against the typed **`powerbi-visuals-api`**. This skill covers the full lifecycle: scaffold → develop → debug → package → certify.

> This is a knowledge plugin. It generates and reviews source files (`visual.ts`, `capabilities.json`, `settings.ts`, `pbiviz.json`) and drives the local `pbiviz` CLI — it does not require any MCP server or cloud credentials.

## Prerequisites

- **Node.js** (current LTS) and **npm**. Install the toolchain globally: `npm i -g powerbi-visuals-tools@latest` (provides the `pbiviz` command).
- A **Power BI Pro** or **Premium Per User (PPU)** account to test in the service, plus an IDE (VS Code recommended).
- **Developer mode** enabled — in Power BI Desktop (*File ▸ Options ▸ Report settings ▸ Develop a visual*, per session) or in the service (*Developer settings ▸ Power BI Developer mode*). See `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/environment-setup.md`.
- For certification: a **Partner Center** account and a public/reviewable **GitHub** repository.

## Choosing how to build

The `pbiviz` SDK is the most powerful path, but not always the right one. Pick the lightest approach that meets the need: **SVG rendered from a DAX measure** (inline sparklines/KPIs in tables, no visual to build), **Deneb** (declarative Vega/Vega-Lite, certified), the **HTML Content** visual, **Charticulator** (no-code), or the full **pbiviz SDK** (TypeScript + D3/React) when you need lifecycle control, a custom format pane, advanced interactivity, or AppSource distribution. The full decision guide and low-code patterns are in `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/building-approaches.md`. The rest of this skill covers the SDK path in depth.

## Deneb — declarative Vega / Vega-Lite (the no-build path)

[Deneb](https://deneb.guide) is a **certified** custom visual that runs **Vega** and **Vega-Lite** JSON specs inside Power BI — no `pbiviz` toolchain, no build, yet with native cross-filtering, tooltips, context menus, and report-theme colors. Reach for it when the chart exists in the Vega ecosystem but not the Power BI core set and the user wants a certified result fast. Key facts:

- **Bind data via the named dataset `dataset`** — `{ "data": { "name": "dataset" } }` (Vega-Lite) or a `dataset` data source (Vega). Field names are the column **display names** from Deneb's *Values* well.
- **Provider:** Vega-Lite (concise `mark`+`encoding`, parameters) for standard charts; Vega (explicit scales/signals/marks) for bespoke layouts and manual interactivity.
- **Cross-filtering:** enable it in Settings, then style on the injected **`__selected__`** field (`'on'`/`'off'`/`'neutral'`). Advanced (Vega-only) mode uses the `pbiCrossFilterSelection` signal with **`pbiCrossFilterApply(event, filter?, options?)`** / **`pbiCrossFilterClear()`** for brush/aggregate selection.
- **Deneb expression functions:** **`pbiColor(index, shade?)`** (theme colors), **`pbiFormat(value, fmt, opts?)`** (Power BI format strings + locale), **`pbiPatternSVG(pattern, fg, bg)`** (texture fills, SVG mode).
- **Responsive:** `"width": "container"`, `"height": "container"`, `"autosize": { "type": "fit", "contains": "padding" }`. Keep shared styling in the **Config** object; reuse charts via the **template** (`usermeta`) system.
- **Certified-build limits:** no external `data.url`, remote images, or external fonts — all data flows through `dataset`.

Run `/pbiviz-deneb` to author or iterate a spec. Full reference: `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/deneb-vega.md`; copy-paste specs: `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/examples/deneb-specs.md`.

## The visual lifecycle at a glance

| Stage | Command / artifact | Reference |
|-------|--------------------|-----------|
| Set up environment | `npm i -g powerbi-visuals-tools@latest`, enable dev mode | `references/environment-setup.md` |
| Scaffold a project | `pbiviz new <name>` | `references/project-structure.md` |
| Declare data + format contract | `capabilities.json` | `references/capabilities.md` |
| Shape the incoming data | `dataViewMappings` + parse `options.dataViews[0]` | `references/dataview-mapping.md` |
| Render & handle lifecycle | `IVisual` class in `src/visual.ts` | `references/visual-api.md` |
| Build the Format pane | `getFormattingModel` + formatting model utils | `references/formatting-model.md` |
| Add interactivity | selection, tooltips, context menu, drill, bookmarks | `references/interactivity.md` |
| Live debug | `pbiviz start` + Developer Visual | command `/pbiviz-debug` |
| Test & lint | ESLint + jasmine/karma unit tests | `references/testing.md` |
| Package | `pbiviz package` → `dist/<name>.pbiviz` | `references/packaging-certification.md` |
| Certify & publish | Partner Center + AppSource | `references/packaging-certification.md` |

## Project structure

`pbiviz new <name>` scaffolds a complete project. The files that matter most:

- **`pbiviz.json`** — visual metadata: internal `name`, `displayName`, unique `guid`, `visualClassName` (must match your `IVisual` class name), `version` (four-part `x.x.x.x`), `apiVersion`, `author`, `assets.icon`, and the paths to `capabilities.json` and `style/visual.less`.
- **`capabilities.json`** — the contract with the host: what data the visual accepts and what appears in the Format pane.
- **`src/visual.ts`** — the `IVisual` implementation (your render code).
- **`src/settings.ts`** — the formatting settings model (Format pane cards/slices).
- **`style/visual.less`** — styles.
- **`assets/icon.png`** — the Visualizations-pane icon, a **20×20 PNG**.
- **`tsconfig.json`**, **`package.json`**, **`package-lock.json`**, **`.eslintrc`**.

Full annotated layout and `pbiviz.json` field reference: `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/project-structure.md`.

## capabilities.json — the data and format contract

`capabilities.json` tells the host what kind of data the visual accepts and what customizable properties appear in the Format pane. From **API v4.6.0** all root properties are optional **except `privileges`, which is required** (use `"privileges": []` when the visual needs no special access).

Root objects:

- **`privileges`** — special access the visual needs: `WebAccess` (external URLs — **must be empty for certification**), `ExportContent` (download to file), `LocalStorage`.
- **`dataRoles`** — the field wells users drag data into. Each has a `name`, `displayName`, and `kind`: `Grouping` (discrete buckets), `Measure` (numeric), or `GroupingOrMeasure`.
- **`dataViewMappings`** — how roles map into a `DataView` (`categorical`, `table`, `matrix`, or `single`), with `conditions` bounding how many fields each role accepts and a `dataReductionAlgorithm` (`top`/`bottom`/`sample`/`window`, default `top` at 1000, max 30000).
- **`objects`** — Format pane properties. Each object/property name must exactly match a card/slice in your formatting model.
- **Feature flags** — `supportsHighlight`, `sorting`, `drilldown`, `expandCollapse`, `supportsLandingPage`, `supportsEmptyDataView`, `supportsKeyboardFocus`, `tooltips`, `advancedEditModeSupport`, `supportsMultiVisualSelection`, `subtotals`, `keepAllMetadataColumns`.

Details and copy-paste blocks: `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/capabilities.md`. Complete files: `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/examples/capabilities-json.md`.

## DataView — reading the data

Each valid `dataViewMappings` entry produces a `DataView` delivered in `update(options)` as `options.dataViews[0]`. Power BI only populates the mapping types you declared.

- **`single`** — one aggregated value (`dataView.single.value`). For KPI cards.
- **`categorical`** — independent groupings + measures (`dataView.categorical.categories[]` and `.values[]`). The most common mapping for charts. Group `values` `by` a series role for hierarchical/series data; read groups with `categorical.values.grouped()`.
- **`table`** — a flat list of rows (`dataView.table.columns[]`, `dataView.table.rows[]`). **Do not assume row order.**
- **`matrix`** — hierarchical rows/columns as a tree of `DataViewMatrixNode` (`dataView.matrix.rows.root`, `.columns.root`, `.valueSources`). Supports expand/collapse of row headers (API 4.1+).

Always null-check the mapping (`if (!dataView?.categorical?.values) return;`) before reading — a user may not have populated every field well. Parsing patterns for every mapping type: `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/dataview-mapping.md`.

## The IVisual API

Every visual is a class implementing `IVisual` (named exactly as `visualClassName` in `pbiviz.json`):

```typescript
export class Visual implements IVisual {
    constructor(options: VisualConstructorOptions) { /* one-time setup */ }
    public update(options: VisualUpdateOptions): void { /* render on every data/size/view change */ }
    public getFormattingModel(): powerbi.visuals.FormattingModel { /* build the Format pane */ }
    public destroy(): void { /* optional cleanup */ }
}
```

- **`constructor(options)`** — runs once. Capture `options.element` (your DOM root) and `options.host` (the `IVisualHost`), and create long-lived services (`createSelectionManager`, `createLocalizationManager`, `colorPalette`, `tooltipService`, `eventService`).
- **`update(options)`** — runs on every change. `options.viewport` is the size, `options.dataViews` the data, `options.type` flags the cause (`Data | Resize | ViewMode | Style | ResizeEnd`). Re-render here.
- **`getFormattingModel()`** — returns the Format pane model (replaces the deprecated `enumerateObjectInstances`).

Host services (selection, tooltips, color palette, `launchUrl`, `persistProperties`, `fetchMoreData`, `storageService`, `eventService`, `displayWarningIcon`, `acquireAADTokenService`) are detailed in `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/visual-api.md`.

**Rendering Events API (required for certification):** call `host.eventService.renderingStarted(options)` at the start of `update`, `renderingFinished(options)` when the DOM is fully drawn, and `renderingFailed(options, reason)` on error. This signals "export to PDF/PowerPoint" and automated tests that rendering is complete.

## The modern Format pane (API 5.1+)

Use API version **5.1 or later** and implement `getFormattingModel`. The recommended approach is the **formatting model utils** (`powerbi-visuals-utils-formattingmodel`):

1. Declare a settings model in `src/settings.ts` extending `formattingSettings.Model`, composed of **cards** (`formattingSettings.SimpleCard` / `CompositeCard`) → optional **groups** → **slices** (`ToggleSwitch`, `NumUpDown`, `Slider`, `ColorPicker`, `ItemDropdown`, `AutoDropdown`, `FontControl`, `TextInput`, `AlignmentGroup`, …). **Card `name` must equal the object name and slice `name` must equal the property name in `capabilities.json`.**
2. In the constructor: `this.formattingSettingsService = new FormattingSettingsService(localizationManager?)`.
3. In `update`: `this.settings = this.formattingSettingsService.populateFormattingSettingsModel(VisualFormattingSettingsModel, options.dataViews);`
4. Implement `getFormattingModel()` → `return this.formattingSettingsService.buildFormattingModel(this.settings);`

Property type ↔ capabilities value-type mapping, composite slices, conditional formatting (per-data-point colors via `dataViewWildcard` selectors + `instanceKind`), and reset-to-default: `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/formatting-model.md`. Full `settings.ts`: `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/examples/formatting-settings.md`.

## Interactivity

- **Selection & cross-filtering** — create an `ISelectionManager` in the constructor and an `ISelectionId` per data point with `host.createSelectionIdBuilder().withCategory(...)` / `.withSeries(...)` / `.withTable(...)` / `.withMatrixNode(...)`. Call `selectionManager.select(id, multiSelect)` on click; reflect `selectionManager.getSelectionIds()` visually. Support Ctrl-click for multi-select.
- **Highlighting** — set `"supportsHighlight": true` and render the `values[i].highlights` array (non-highlighted portions dimmed) when other visuals cross-filter yours.
- **Tooltips** — declare `"tooltips"` in capabilities and use `host.tooltipService` (or `powerbi-visuals-utils-tooltiputils`) to show/move/hide tooltips on pointer events.
- **Context menu** — `selectionManager.showContextMenu(selectionId, {x, y}, dataRole?)` on `contextmenu`. The `dataRole` argument is required when the visual supports `drilldown` or `expandCollapse`.
- **Drill-down / expand-collapse**, **bookmarks** (register a selection callback, restore on `update`), **landing page** (`supportsLandingPage` + `supportsEmptyDataView`), **`launchUrl`**, **local storage**, and the **color palette** are all covered in `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/interactivity.md`.

## Testing, linting, and security

Certification requires `eslint`, `eslint-plugin-powerbi-visuals`, and `typescript` in `package.json`, a clean `npm audit` (no high/moderate), and no JS console errors. Unit tests use jasmine + karma with `powerbi-visuals-utils-testutils`. Manipulate the DOM safely — **never** use `innerHTML`/`D3.html()` with user data, and avoid `eval`/`Function`/dynamic `setTimeout`. See `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/testing.md`.

## Packaging & certification

- **Package:** `pbiviz package` writes `dist/<name>.pbiviz`. The `description` field in `pbiviz.json` must be filled in or the command fails. Bump the four-part `version` before each package.
- **Certify:** publish to AppSource via **Partner Center**, then optionally request certification. Requirements: latest API, a lowercase **`certification`** Git branch matching the submitted package, OSS-only libraries, Rendering Events API support, **no external network access** (`WebAccess` empty/omitted), no minified code, and a clean `pbiviz package --certification-audit`. Full checklist: `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/packaging-certification.md`.

## Utility packages

Microsoft ships helper packages so you don't reinvent common chart plumbing: `powerbi-visuals-utils-formattingmodel` (Format pane), `-dataviewutils`, `-formattingutils` (number/date formatting + text measurement), `-svgutils`, `-chartutils` (axes/legend), `-colorutils`, `-interactivityutils`, `-tooltiputils`, `-typeutils`, and `-testutils`. Reference + common errors: `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/utils-and-troubleshooting.md`.

## Reference files

| File | Path | Content |
|------|------|---------|
| Building approaches | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/building-approaches.md` | Choosing SDK vs Deneb vs SVG-via-DAX vs HTML Content vs Charticulator; React + pbiviz MCP |
| Deneb (Vega/Vega-Lite) | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/deneb-vega.md` | Deneb editor, `dataset` binding, cross-filter (`__selected__`, `pbiCrossFilterApply`), `pbiColor`/`pbiFormat`/`pbiPatternSVG`, themes, templates, certified limits |
| Environment setup | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/environment-setup.md` | Node, pbiviz install, dev mode, account/SSL setup |
| Project structure | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/project-structure.md` | Folder layout, `pbiviz.json`, `tsconfig`, `package.json` |
| Capabilities | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/capabilities.md` | privileges, dataRoles, mappings, objects, feature flags |
| DataView mapping | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/dataview-mapping.md` | single/categorical/table/matrix parsing, reduction, conditions |
| Visual API | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/visual-api.md` | IVisual lifecycle, host services, rendering events |
| Formatting model | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/formatting-model.md` | Format pane, utils, slice types, conditional formatting |
| Interactivity | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/interactivity.md` | selection, tooltips, context menu, drill, bookmarks, landing page |
| Testing | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/testing.md` | ESLint, unit tests, debugging, security rules |
| Packaging & certification | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/packaging-certification.md` | pbiviz package, AppSource, certification checklist |
| Utils & troubleshooting | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/references/utils-and-troubleshooting.md` | utility packages, common errors and fixes |

## Example files

| File | Path | Content |
|------|------|---------|
| Visual (bar chart) | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/examples/visual-ts-barchart.md` | Complete `visual.ts`: IVisual, D3, selection, tooltips, rendering events |
| capabilities.json | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/examples/capabilities-json.md` | Complete capabilities for categorical, table, and matrix visuals |
| Formatting settings | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/examples/formatting-settings.md` | `settings.ts` cards/groups/slices + conditional formatting |
| Config files | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/examples/pbiviz-and-config.md` | `pbiviz.json`, `tsconfig.json`, `package.json`, `.eslintrc` |
| Scaffold walkthrough | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/examples/scaffold-walkthrough.md` | End-to-end: new → develop → start → package → certify |
| Deneb specs | `${CLAUDE_PLUGIN_ROOT}/skills/powerbi-custom-visuals/examples/deneb-specs.md` | Copy-paste Vega/Vega-Lite specs: cross-filter bar, rolling-mean line, bullet, pattern fills, advanced-mode Vega, theme config, template `usermeta` |

