Tutorial

Build a Power BI Custom Visual with Claude or ChatGPT: DIY vs a Generator

· By Nawaf Sharaf · Founder & Reliability Engineer

TL;DR

You can build a Power BI custom visual with Claude or ChatGPT: install Node, run pbiviz new, paste the generated visual.ts, settings.ts, capabilities.json and style.less, and run pbiviz package. Expect the first draft to fail type-checking. The errors are predictable: string operations on IEnumMember (TS2339), unwrapped settings slices, a missing generic on populateFormattingSettingsModel (TS2739), an un-annotated cards array (TS2344), options referenced outside update() (TS2552), the wrong ISelectionId import, d3.event, innerHTML. DIY is free and educational; a generator is worth paying for when you want the loop closed automatically.

A finance analyst on r/PowerBI asked in mid-2026 whether anyone had built custom visuals with Claude: "My background is finance and not computer science, so the idea of creating anything appeared overwhelming." SQLBI published a long article on the same question. The honest answer is yes, with a toolchain to install and a specific set of compiler errors to fix. This guide is written from the other side of that loop: VizForge's build server runs the real TypeScript compiler and pbiviz against every LLM-written visual it produces, and its repair rules are a catalogue of what models get wrong. Those rules are quoted here so you can fix them yourself. Then the guide weighs DIY against a generator, including VizForge, without pretending the generator is free.

What a Power BI custom visual actually is

A .pbiviz file is a zip containing compiled JavaScript, a capabilities.json, assets and a pbiviz.json manifest. You produce it with Microsoft's powerbi-visuals-tools (the pbiviz CLI) from a TypeScript project built against powerbi-visuals-api. Four files carry the design:

  1. capabilities.json: data roles (what fields the author drops in), data view mappings (how Power BI shapes the data), and formatting objects (the cards in the format pane).
  2. visual.ts: a class implementing IVisual with a constructor and an update(options) method that is called on every interaction, so it must be idempotent.
  3. settings.ts: the formatting model, typically built with powerbi-visuals-utils-formattingmodel.
  4. style.less: the stylesheet compiled into the bundle.

Everything else (tsconfig, webpack config, package.json) comes from the template that pbiviz new creates. Learn's "Develop custom visuals" page documents the setup and the package step. You also need a tenant where you can test: the developer visual in the Service requires an admin to enable it, and importing a .pbiviz requires the "Allow visuals created using the Power BI SDK" setting.

The DIY loop, step by step

  1. Install Node.js LTS and run npm i -g powerbi-visuals-tools.
  2. pbiviz new MyChart, then cd MyChart and npm install. Open the folder in VS Code.
  3. Prompt the model with the four-file contract above, the API version from pbiviz.json, and the exact data roles you want. Ask for D3 v7 and the formatting model utils. Paste the results into src/visual.ts, src/settings.ts, capabilities.json and style/visual.less.
  4. Run npx tsc --noEmit. Read the first error, paste it back to the model with the surrounding code, repeat. Budget 3 to 10 rounds for a first visual.
  5. pbiviz start for live reload against the developer visual in the Service, or skip to pbiviz package and import the .pbiviz into Desktop.
  6. Fix the manifest: pbiviz.json needs a unique GUID per visual and a filled-in author name and email, or packaging fails with an unhelpful error. The visual name must be letters and numbers only.
  7. Test: empty data view, one row, 10,000 rows, cross-filtering from another visual, right-click context menu, the format pane cards, resize.

Step 4 is where the time goes, and where the list below saves it.

The errors every model makes (from a real validator)

VizForge's validator type-checks each generated visual with tsc against the real SDK packages and, before handing the compiler output to an LLM repair round, applies a set of deterministic fixes (d3.event, innerHTML, missing rendering events, the empty-data-view guard and similar). Those fixers, plus the BEFORE/AFTER patterns its repair prompt feeds the model, are the clearest evidence of what LLM-written visuals get wrong. The seven that cost the most time:

  1. String operations on dropdown values. Any formatting property typed IEnumMember | ILocalizedItemMember is an object with a .value field, not a string. Calling .toLowerCase() or comparing it to "on" fails with TS2339 or TS2345. Fix: wrap the read in String(), as in String(card.dropdown.value.value).
  2. Unwrapped settings slices. Every property on a settings card is a Slice object; settings.label.fontSize is a NumUpDown, not a number. Fix: .value, and for colour pickers .value.value.
  3. Missing generic on populateFormattingSettingsModel. Without the explicit type argument TypeScript infers the abstract Model and the build fails with TS2739 and TS2345. Fix: add the explicit type argument <VisualFormattingSettingsModel> to the call, and declare the field with the concrete type.
  4. Un-annotated cards array. cards = [new A(), new B()] infers a union type that does not satisfy the base constraint (TS2344). Fix: cards: FormattingSettingsCard[] = [...].
  5. options out of scope. Helper methods call this.events.renderingFinished(options) where options is only a parameter of update(), producing TS2552. Fix: pass it in or store it.
  6. The wrong ISelectionId. powerbi.extensibility.ISelectionId and powerbi.visuals.ISelectionId are different types; mixing them fails selection code with TS2339 (getKey, includes or hasIdentity "does not exist on type ISelectionId"). Fix: import ISelectionId = powerbi.visuals.ISelectionId.
  7. D3 version drift and banned DOM APIs. d3.event was removed in v7 (callbacks are (event, d)); d3.nest() is gone; and pbiviz's ESLint rule blocks innerHTML and outerHTML. Fix: v7 signatures, d3.group(), textContent.

Other repairs on the list: injecting renderingStarted and renderingFinished, adding the null check for an empty data view at the top of update(), removing createTooltipServiceWrapper (its types break the build) in favour of host.tooltipService, using any generics on d3.Selection parameters because webpack's type-check is stricter than esbuild's, removing the type annotation on topLevelSlice (TS2416), and correcting the package name powerbi-visuals-formatting-utils to powerbi-visuals-utils-formattingutils.

settings-fixes.ts

// BEFORE (fails pbiviz package)
const mode = settings.layout.mode.value.toLowerCase();          // TS2339
this.formattingSettings = this.formattingSettingsService
  .populateFormattingSettingsModel(VisualFormattingSettingsModel, dataView); // TS2739
cards = [this.generalCard, this.labelCard];                      // TS2344

// AFTER
const mode = String(settings.layout.mode.value.value).toLowerCase();
this.formattingSettings = this.formattingSettingsService
  .populateFormattingSettingsModel<VisualFormattingSettingsModel>(
    VisualFormattingSettingsModel, dataView);
cards: FormattingSettingsCard[] = [this.generalCard, this.labelCard];

import ISelectionId = powerbi.visuals.ISelectionId;             // not extensibility.ISelectionId
svg.selectAll("rect").on("click", (event: MouseEvent, d: Datum) => { /* v7 */ });

A prompt that gets you a compiling first draft more often

Models do better when the contract is explicit. Include: the API version from your pbiviz.json; "import powerbi from 'powerbi-visuals-api' with type aliases, no named imports"; "D3 v7: (event, d) callbacks, d3.group not d3.nest"; "settings use powerbi-visuals-utils-formattingmodel; call populateFormattingSettingsModel with an explicit generic; annotate cards as FormattingSettingsCard[]; do not type-annotate topLevelSlice"; "read dropdown values as .value.value and wrap with String()"; "null-check options.dataViews first in update(), call renderingStarted and renderingFinished"; "selection with createSelectionIdBuilder, selectionManager.select, showContextMenu on right-click, dim unselected to 0.3 opacity"; "no innerHTML". Then the data roles and the behaviour. The prompt is long because the SDK's rules are specific; a short prompt gets a pretty chart that does not compile.

DIY versus a generator, honestly

DIY with Claude or ChatGPT costs nothing beyond the model subscription you may already have, teaches you the SDK, and leaves you fully in control. It costs time: the toolchain, the compile loop, the tenant setup, and testing behaviours the model forgot. For a developer who will build several visuals, it is the right investment.

A generator closes the loop you would otherwise run by hand. VizForge classifies the request, retrieves SDK patterns, generates the four files, runs tsc --noEmit against pre-installed SDK packages, applies its deterministic fixes, sends remaining errors back to the model with the compiler output and those repair patterns for up to three rounds (escalating to a stronger model after the first), runs pbiviz package, and serves the .pbiviz plus source. Then a refinement loop takes "make the labels inside the bars" and edits the existing code rather than regenerating. The costs are real: credits (1 to 5 per visual from one-time packs, 5 free on signup), an uncertified output like any .pbiviz, and a product's opinions about structure. Microsoft's own October 2026 release added a Power BI Report skill that can add supported custom visuals to a report and bind them; it places existing visuals, it does not write new ones, so it complements either route.

Choose DIY if you are a developer, want certification eventually (you will submit your own code), or need to own the toolchain. Choose a generator if you are an analyst who needs the visual this week, or a developer who wants the compile loop handled and the code delivered.

Governance applies to both routes

Whatever writes the code, the result is a custom visual your tenant has to permit. Check "Allow visuals created using the Power BI SDK" in the Fabric admin portal, and whether "Add and use certified visuals only" is on; if it is, an uncertified visual renders in the Service only after an admin adds it to the organizational visuals store, which Microsoft documents as overriding the tenant settings (Desktop follows the matching Group Policy keys). Keep the source in a repository, record the GUID, and inspect the package before import: the free pbiviz Inspector reads the API version, data roles and declared privileges in your browser.

FAQ

Can ChatGPT or Claude build a Power BI custom visual?

Yes. Both can write the four source files for a visual built with the Power BI visuals SDK. Expect the first draft to fail type-checking on predictable errors (IEnumMember string operations, unwrapped settings slices, a missing generic on populateFormattingSettingsModel, the wrong ISelectionId import) and budget several compile-and-fix rounds.

What do I need installed to build a .pbiviz?

Node.js LTS and powerbi-visuals-tools (the pbiviz CLI). Create a project with pbiviz new, install dependencies with npm install, type-check with tsc, and package with pbiviz package. Testing with the developer visual in the Service needs an admin to enable it.

Why does my LLM-generated visual fail with TS2739 or TS2344?

TS2739 usually means populateFormattingSettingsModel was called without the explicit generic, so TypeScript inferred the abstract Model class. TS2344 means the cards array was not annotated as FormattingSettingsCard[], so a union of your card types was inferred. Both are one-line fixes.

Is a visual built with AI certified?

No. Certification is a Microsoft review process for Marketplace visuals. Anything you build or generate is an uncertified custom visual until you submit it; your tenant must allow SDK visuals for it to render.

Is a generator better than doing it myself?

It is faster and it closes the compile-repair loop automatically, at the cost of credits and less control over the code structure. If you are a developer who will build many visuals, DIY pays off. If you need one visual this week and want the source delivered, a generator is the shortcut.

Sources

Related reading

About the author

Nawaf Sharaf · Founder & Reliability Engineer

Nawaf is a reliability engineer and long-time Power BI practitioner, and the founder of VizForge. He builds the product solo — the AI generation pipeline, the visual code generator, and this site — from the perspective of someone who has spent years shipping Power BI reports in production.

More about Nawaf Sharaf and VizForge · VizForge home

All VizForge articles →

Cite this article as:

VizForge. “Build a Power BI Custom Visual with Claude or ChatGPT: DIY vs a Generator.” November 4, 2026. https://vizforge.ai/blog/build-power-bi-custom-visual-with-claude-chatgpt

Make the report.
Then make it yours.

The Design Studio, templates and tools are free with no account (exports carry a small VizForge mark until you sign in). Sign up when you want AI: 5 credits on us, no card.

Generated visuals are type-checked and built before you download them; one that fails the quality SLA refunds its credits. See all features or pricing.