Power BI Custom Visuals Skill
Build new Power BI custom visuals from natural language using the
powerbi-visuals-tools SDK. Iteration is agent-driven on TypeScript
compile errors; visual correctness is checked once at the end with the
user.
This skill produces a .pbiviz package and embeds it into a PBIR report
via pbi visual import-custom. The TypeScript project lives next to
the .pbip folder, never inside it.
Prerequisites
The skill needs Node.js on PATH and the powerbi-visuals-tools npm
package. Both are installed on first run with the user's consent; never
silently.
Pinned versions (override via env vars if needed):
powerbi-visuals-tools@^5.6.0(env:PBIVIZ_VERSION)powerbi-visuals-api@^5.11.0(pinned in scaffoldedpackage.json)
The skill's edit patterns and AGENTS.md crib were written against these versions. Bump deliberately.
First-run prerequisite check
# 1. Probe Node
node --version || true
If node is missing, ask the user:
Node.js isn't installed. Custom visual development needs it. Install now? (yes/no)
- Windows:
winget install OpenJS.NodeJS.LTS- macOS:
brew install node- Linux: use your package manager or nvm
If user says yes, run the install command appropriate for their OS and re-probe. If user says no, stop the skill with a clear message.
pbiviz itself runs through npx --yes powerbi-visuals-tools@^5.6.0,
so there's no global install of the CLI itself. The first npx
invocation will fetch and cache it locally.
Discovery: existing project vs fresh scaffold
The skill's first action is always discover, not scaffold.
- Locate the user's PBIR project (the
.pbipfolder or its.Reportsibling). - Look for sibling directories matching
*-visual/containing apbiviz.json. - Branch:
- None found → fresh scaffold flow (see "Plan-then-code" below).
- One found → load it; jump to edit-validate-package-import loop.
- Multiple found → ask the user which one; do not guess.
This means re-invoking the skill on day 2 picks up where day 1 left off.
Plan-then-code (fresh scaffolds only)
For fresh scaffolds, before touching any code:
- Read the user's natural-language spec.
- Output a 5-line plan stating:
- Data roles needed (and which is
GroupingvsMeasure). - Primary render approach (DOM, SVG, D3, canvas, charting lib).
- Formatting properties to expose in the right pane.
- npm dependencies beyond the scaffold defaults (justify each one; see "npm dependency policy" below).
- Data roles needed (and which is
- Wait for explicit user OK before generating code.
The plan exists to foreclose the most expensive failure mode: getting
the data role declarations wrong in capabilities.json and discovering
it 15 turns later. Skipping the plan is not an optimization.
For sustained-authoring edits to an existing project, skip the plan step and iterate directly.
Scaffold
Working directory: parent of the user's PBIR project (sibling, never
inside .Report or .pbip).
Naming constraint: pbiviz new rejects names containing anything
other than letters and digits. No hyphens, no underscores, no dots.
If the user's spec name has those (e.g. "my-gauge-visual"), strip them
before scaffolding (e.g. mygaugevisual). The friendly displayName in
pbiviz.json can still carry spaces and punctuation.
# Inside <project-parent>/
npx --yes powerbi-visuals-tools@^5.6.0 new <visualname>
cd <visualname>
Auto-strip the circle-card demo
pbiviz new produces a working "circle card" demo. Strip it before
handing off to iteration:
- Open
src/visual.ts. The constructor andupdate()method contain demo-specific code (creates an<svg>with a<circle>and<text>that displays a number). - Replace the
update()body with a single comment like// TODO: build per specand remove the SVG/circle helpers. - Open
capabilities.json. Replace the demo'sdataRoles(typicallycategoryandmeasurefor the circle demo) with the data roles you planned. Empty theobjectsblock; add formatting properties per plan. - Open
style/visual.less. Empty it. - Bump
apiVersioninpbiviz.jsonandpowerbi-visuals-apiinpackage.jsonto the pinned version above (only if the scaffold doesn't already match).
Fill required pbiviz.json metadata
pbiviz package strict-validates four fields and fails to build
if any are missing. The scaffold leaves them blank, so populate them
before any package step or you'll waste iteration turns chasing a
"not specified" error that has nothing to do with the code:
| Field | Source |
|---|---|
visual.description |
One-line summary derived from the user's spec |
visual.supportUrl |
"https://example.com" placeholder (real URL needed for AppSource publish) |
author.name |
git config user.name (run it; fall back to "pbi-cli user" if empty) |
author.email |
git config user.email (run it; fall back to "noreply@example.com" if empty) |
Do this immediately after auto-stripping the circle-card demo, once,
in a single edit to pbiviz.json. If the user later wants to publish to
AppSource, they replace these values themselves; the skill's auto-bump
on version doesn't touch any other field, so user edits stick.
Drop in AGENTS.md
Write AGENTS.md at the project root using the template at
./AGENTS-template.md (bundled alongside this SKILL.md). It tells
future-Claude what's editable, what's locked, and gives 30 lines of
SDK pattern crib. Always drop this file. It's the biggest single
lever on first-iteration success.
Inner loop: agent-driven validation
For each user-requested change:
Edit the relevant files (see AGENTS.md for editable vs locked).
Run fast type check:
npx tsc --noEmit -p tsconfig.jsonIf errors:
- Read every error verbatim.
- Fix them.
- Re-run
tsc --noEmit. - Repeat with progress discipline (see "Failure cap" below).
If clean: stop. Don't package every loop. Package only when the user asks "show me" or you're at a natural completion point.
Do not run pbiviz start in v1. The dev server's only purpose is
sub-second hot-reload while a human watches Desktop, which is not how
this loop works.
Failure cap (self-policed)
To prevent infinite loops on cryptic SDK errors:
- 5-turn no-progress cap. "Progress" = error count strictly decreased OR the qualitative root cause changed. After 5 turns with no progress, stop.
- Oscillation detection. If the same error appears, gets fixed, and reappears within 3 turns, stop.
- On stop: dump the current
tscoutput verbatim to the user and offer two concrete hypotheses for the root cause. Ask the user to guide. Do not silently keep trying.
Package and import
When the inner loop is clean and you (or the user) are ready to see it:
# Bump patch version so Power BI Desktop's GUID+version cache invalidates
pbi-cli internal pbiviz-bump # see note below
# Package
npx --yes powerbi-visuals-tools@^5.6.0 package
# .pbiviz lands in dist/<visualname>.<version>.pbiviz
# Import into the user's report
pbi visual import-custom dist/<visualname>.<version>.pbiviz --replace
Version auto-bump. Use the helper exposed via the skill (or call
pbiviz_bump_patch() from pbi_cli.core.custom_visual_backend
programmatically). It increments the patch number in pbiviz.json. If
the user has set a non-<int>.<int>.<int> version manually, the bump
is skipped and the manual value is respected.
Why the bump matters. Power BI Desktop caches custom visuals by GUID + version. Repackaging without bumping risks Desktop serving stale code on next open. The bump is cheap insurance.
If pbiviz package itself fails (capabilities schema invalid,
unsupported API features, etc.), feed the error to Claude the same way
as tsc errors. Subject to the same failure cap.
Eyeball handoff
After a successful import, hand off to the user once:
Imported. Open the report in Power BI Desktop, place the visual on a page, bind data, and tell me what looks wrong. I'll iterate from there.
If the user reports issues, repeat the inner loop (skip Plan since the project exists). If the user reports it looks right, you're done.
npm dependency policy
Custom visuals routinely need libraries (D3, Lodash, charting libs). Installing arbitrary packages from npm is a real supply-chain concern under the user's identity, so this skill operates under an allowlist.
Allowlist (install without asking)
Any package in this list, at or above the version floor, may be
installed via npm install <pkg> (or --save-dev) without prior
user confirmation:
| Package | Floor |
|---|---|
d3 |
^7.0.0 |
d3-array |
^3.0.0 |
d3-axis |
^3.0.0 |
d3-color |
^3.0.0 |
d3-format |
^3.0.0 |
d3-interpolate |
^3.0.0 |
d3-scale |
^4.0.0 |
d3-selection |
^3.0.0 |
d3-shape |
^3.0.0 |
d3-time-format |
^4.0.0 |
lodash |
^4.17.0 |
date-fns |
^3.0.0 |
powerbi-visuals-utils-formattingmodel |
^6.0.0 |
powerbi-visuals-utils-tooltiputils |
^6.0.0 |
powerbi-visuals-utils-chartutils |
^6.0.0 |
powerbi-visuals-utils-dataviewutils |
^6.0.0 |
@types/d3 |
^7.0.0 |
@types/lodash |
^4.14.0 |
Type-only packages follow the same allowlist; there is no separate
devDependencies rule.
Off-allowlist installs
Any package not on the allowlist (including any version below a
floor) requires explicit user confirmation before running
npm install. State all four:
- Package name and exact version range.
- Why it's needed for the user's spec (specific feature, not "it might be useful").
- Bundle size impact estimate (k or kk gzipped; check bundlephobia.com if uncertain).
- Alternative considered (an allowlisted package or hand-rolled code) and why it's worse.
Then wait for explicit "yes" / "go ahead". Don't proceed on silence.
This blocks the worst failure mode: hallucinating a typo'd package
name (e.g. d3-scaling) and installing typosquat malware. Typos won't
match the allowlist; the confirm step exposes them.
Concrete CLI surface used by this skill
Provided by pbi-cli:
pbi visual import-custom <pbiviz-file> [--replace] [--no-sync]pbi visual list-custompbi visual remove-custom <guid-or-name> [--no-sync]
Provided by npx --yes powerbi-visuals-tools@^5.6.0:
new <name>— scaffold projectpackage— produce.pbivizzip--create-cert— generate dev cert (only needed on firstpackageinvocation per machine; pbiviz prompts automatically)
That's the entire toolchain. No other commands needed in v1.
What's deliberately out of scope (v1)
pbiviz startlive preview server.- AppSource (public) custom visual registration via GUID-only
(separate
register-publiccommand, deferred). - Publishing to AppSource / Partner Center.
- Multi-visual workspaces, monorepo patterns.
- Custom visual themes / sharing across reports.
If the user asks for any of the above, surface that it's outside v1 scope and offer to file a follow-up.