Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Vibe-code Power BI custom visuals end-to-end: scaffold a TypeScript project, iterate on src/visual.ts and capabilities.json, validate with the Power BI Visuals SDK toolchain, package to .pbiviz, and import into a PBIR report. Invoke this skill whenever the user mentions "custom visual", "pbiviz", "powerbi-visuals-tools", "IVisual", "build a chart Power BI doesn't have", "developer visual", or wants a custom-built visual that the built-in Power BI library can't render. NOT for: changing colors /
.claude/skills/minasaad1-power-bi-custom-visuals/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 65% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 148% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 158% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 114% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 384% | 0% |
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.
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 scaffolded package.json)The skill's edit patterns and AGENTS.md crib were written against these versions. Bump deliberately.
bash# 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.
The skill's first action is always discover, not scaffold.
.pbip folder or its .Reportsibling).
*-visual/ containing apbiviz.json.
This means re-invoking the skill on day 2 picks up where day 1 left off.
For fresh scaffolds, before touching any code:
Grouping vs Measure).see "npm dependency policy" below).
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.
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.
bash# Inside <project-parent>/ npx --yes powerbi-visuals-tools@^5.6.0 new <visualname> cd <visualname>
pbiviz new produces a working "circle card" demo. Strip it before handing off to iteration:
src/visual.ts. The constructor and update() method containdemo-specific code (creates an <svg> with a <circle> and <text> that displays a number).
update() body with a single comment like// TODO: build per spec and remove the SVG/circle helpers.
capabilities.json. Replace the demo's dataRoles (typicallycategory and measure for the circle demo) with the data roles you planned. Empty the objects block; add formatting properties per plan.
style/visual.less. Empty it.apiVersion in pbiviz.json and powerbi-visuals-api inpackage.json to the pinned version above (only if the scaffold doesn't already match).
pbiviz.json metadatapbiviz 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.
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.
For each user-requested change:
bash npx tsc --noEmit -p tsconfig.json
tsc --noEmit.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.
To prevent infinite loops on cryptic SDK errors:
OR the qualitative root cause changed. After 5 turns with no progress, stop.
reappears within 3 turns, stop.
tsc output verbatim to the userand offer two concrete hypotheses for the root cause. Ask the user to guide. Do not silently keep trying.
When the inner loop is clean and you (or the user) are ready to see it:
bash# 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.
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.
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.
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.
Any package not on the allowlist (including any version below a floor) requires explicit user confirmation before running npm install. State all four:
"it might be useful").
bundlephobia.com if uncertain).
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.
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 .pbiviz zip--create-cert — generate dev cert (only needed on firstpackage invocation per machine; pbiviz prompts automatically)
That's the entire toolchain. No other commands needed in v1.
pbiviz start live preview server.(separate register-public command, deferred).
If the user asks for any of the above, surface that it's outside v1 scope and offer to file a follow-up.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 30,140 | 8,657 | -71% | 1 | 1 | 0% | 6,205 | 3,875 | -38% | 0 | 0 | — |
case-02 | fail→fail | 18,381 | 6,685 | -64% | 1 | 1 | 0% | 3,754 | 3,859 | +3% | 0 | 0 | — |
case-03 | fail→fail | 8,341 | 5,371 | -36% | 1 | 1 | 0% | 1,515 | 3,564 | +135% | 0 | 0 | — |
case-04 | pass→pass | 9,731 | 3,749 | -61% | 1 | 1 | 0% | 1,782 | 3,989 | +124% | 0 | 0 | — |
case-05 | pass→pass | 12,487 | 2,641 | -79% | 1 | 1 | 0% | 2,338 | 3,824 | +64% | 0 | 0 | — |
case-06 | fail→pass | 11,625 | 2,598 | -78% | 1 | 1 | 0% | 2,296 | 3,788 | +65% | 0 | 0 | — |
case-07 | fail→pass | 7,970 | 1,634 | -79% | 1 | 1 | 0% | 1,453 | 3,607 | +148% | 0 | 0 | — |
case-08 | fail→pass | 11,366 | 2,650 | -77% | 1 | 1 | 0% | 1,472 | 3,797 | +158% | 0 | 0 | — |
case-09 | fail→fail | 11,476 | 4,034 | -65% | 1 | 1 | 0% | 1,951 | 3,974 | +104% | 0 | 0 | — |
case-10 | fail→pass | 9,313 | 2,262 | -76% | 1 | 1 | 0% | 1,711 | 3,660 | +114% | 0 | 0 | — |
case-11 | fail→fail | 8,944 | 2,058 | -77% | 1 | 1 | 0% | 1,373 | 3,660 | +167% | 0 | 0 | — |
case-12 | fail→fail | 11,688 | 2,095 | -82% | 1 | 1 | 0% | 1,938 | 3,601 | +86% | 0 | 0 | — |
case-13 | fail→pass | 4,592 | 2,040 | -56% | 1 | 1 | 0% | 739 | 3,580 | +384% | 0 | 0 | — |
case-14 | fail→pass | 10,537 | 1,986 | -81% | 1 | 1 | 0% | 1,570 | 3,673 | +134% | 0 | 0 | — |
case-15 | fail→pass | 13,755 | 2,886 | -79% | 1 | 1 | 0% | 2,169 | 3,741 | +72% | 0 | 0 | — |
case-16 | pass→pass | 7,620 | 3,919 | -49% | 1 | 1 | 0% | 1,425 | 3,892 | +173% | 0 | 0 | — |
case-17 | fail→pass | 4,217 | 1,729 | -59% | 1 | 1 | 0% | 678 | 3,575 | +427% | 0 | 0 | — |
case-18 | fail→pass | 8,086 | 5,072 | -37% | 1 | 1 | 0% | 1,257 | 4,044 | +222% | 0 | 0 | — |
case-19 | fail→pass | 9,421 | 1,981 | -79% | 1 | 1 | 0% | 1,370 | 3,601 | +163% | 0 | 0 | — |
case-20 | fail→pass | 14,828 | 4,572 | -69% | 1 | 1 | 0% | 2,908 | 4,009 | +38% | 0 | 0 | — |
case-21 | fail→pass | 5,469 | 3,993 | -27% | 1 | 1 | 0% | 866 | 3,843 | +344% | 0 | 0 | — |
case-22 | fail→fail | 15,060 | 13,479 | -10% | 1 | 1 | 0% | 2,231 | 5,683 | +155% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 20 counted toward the lift figure. The other 2 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +55 percentage points is the difference between those two pass rates over the 20 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.