Debugging

The seven errors every Tableau extension throws

Tableau's extension errors are terse to the point of rudeness. Most of them are one line, several are just a blank rectangle where your extension should be, and one is a hex code. They're also finite. Here are the seven you'll actually hit, what each one really means, and the fix.

9 min readDashboard & viz extensions

First, the thing nobody tells you: errors are step three, not failure. Building an extension is a loop: write it, load it in Tableau, read the error, fix, reload. One or two cycles to clear is normal even when you know what you're doing. If you're pasting an error into an AI assistant, paste the exact text; these messages are terse but they are specific, and the specificity is the only thing that makes them fixable.

01 · My manifest change did nothing

01You edited the .trex, reloaded, and Tableau is still running the old one.
CauseHTML and JS hot-reload. Manifests do not. When you add an extension, Tableau reads the .trex once and embeds a copy in the workbook. The file on disk stops mattering.
FixFor HTML/JS edits, right-click the extension → Reload. For any manifest change, remove and re-add the extension. Re-adding reopens the last .trex directory you used, so it's about four seconds.

This one is worth internalising early, because it disguises itself as every other error on this list. You'll fix a genuine manifest bug, see the identical failure, and conclude the fix didn't work, when actually Tableau never read it.

02 · It loads, but it's blank

02The object is there. It has a border. It contains nothing.
CauseUsually the dashboard has no worksheet with a measure on it yet, or your code is reading a worksheet that isn't the one you think it is.
FixPut a worksheet with a measure on the dashboard. In code, find the worksheet and the measure by name or by type, never by hard-coded index.

The deeper fix is to make this error impossible to see. An extension that renders nothing when its inputs are missing is indistinguishable from an extension that's broken: by you, and by every colleague who opens the workbook later. Always render a real empty state:

// Not a blank box, a sentence that says what to do.
if (!worksheet || !measures.length) {
  show('Add a worksheet with a measure to this dashboard');
  return;
}

Ten seconds of work that converts a support conversation into something the user fixes themselves.

03 · "Missing XML element"

03The manifest won't parse at all.
CauseA required element is missing from the .trex. This is the single most common first-load error, and it's almost always a hand-written or AI-generated manifest that looked complete.
FixKeep a known-good manifest in the repo and diff against it rather than writing one from memory. The required set, in order: default-locale, name, description, author, min-api-version, source-location, icon.

Order genuinely matters: the schema is a sequence, not a set. A manifest with every required element in the wrong order fails exactly like one that's missing an element. The full anatomy is here.

04 · It won't load at all, just spins

04No error, no content, no timeout. Just a spinner, forever.
CausePort contention. You have more than one extension project running on local servers at once, and the manifest's url is pointing at a port that's now serving something else, or nothing.
FixOne project, one port. Shut down every other local server. Confirm the port in the manifest is the port you're actually serving from. Restart clean.

This one cost me seventy minutes during a live build. There is no error message anywhere in the chain (not in Tableau, not in the terminal) because from Tableau's point of view nothing has gone wrong yet. It's still waiting.

The reliable habit: before debugging anything else, open the manifest's URL in a plain browser tab. If your extension doesn't render there, it will never render in Tableau, and the problem is in your server, not your extension.

05 · Data reads empty when there's clearly data

05The worksheet is full of numbers. Your extension sees none of them.
CauseYou're reading a column by index. getSummaryDataAsync returns columns in pill order, which changes every time the user rearranges the sheet.
FixFind columns by dataType ('float' / 'int') or by name. Use nativeValue for arithmetic. formattedValue is a display string and will quietly poison your maths.
// Breaks the moment someone reorders the pills.
const total = rows.reduce((a, r) => a + r[2].nativeValue, 0);

// Survives it.
const m = table.columns.find(c => c.dataType === 'float' || c.dataType === 'int');
const total = rows.reduce((a, r) => a + r[m.index].nativeValue, 0);

06 · FD722608: the content-model error

06missing elements in content model '(… source-location,icon,permissions?,…)'
CauseYour manifest has no <icon> element. Some Tableau builds list it as required (note there's no ? after icon in that content model, while permissions has one), so leaving it out fails to parse, even though plenty of older templates omit it.
FixAdd an empty <icon/> immediately after </source-location>. That's it. No attributes, no content.
  <source-location>
    <url>https://your-host.example.com/index.html</url>
  </source-location>
  <icon/>   <!-- this line. this is the whole fix. -->
</dashboard-extension>

Learning to read that error message is worth more than the fix itself, because the same code covers a whole family of problems. The parenthesised list is the schema: elements in required order, a ? marking the optional ones. Whatever's in that list and missing from your file is your answer.

Building a viz extension? FD722608 has a second, nastier cause there: an encoding-icon token that isn't in Tableau's enumeration. The published docs list tokens that the parser rejects, and it reports only the first bad one, so you fix and re-fail several times over. More on viz extensions here.

07 · Fine on desktop, broken once published

07It works perfectly in Tableau Desktop. On Server or Cloud it's an empty rectangle.
CauseEither the .trex still points at localhost, or the URL isn't on the site's safe list. Desktop is far more permissive than Server: that gap is the whole error.
FixShip the production manifest pointing at a real HTTPS URL, and have a site admin add that URL to the extension safe list.
The one hard rule

HTTPS, with a real certificate.

Tableau will not load a production extension over plain http, and self-signed certificates fail too. Everything else on this list is a bug you can argue with. This one is not negotiable, and real certificates are free.

One related non-bug, because people file it as one: extensions don't render in PDF exports or printed images on Server and Cloud. Blank space where the extension was is expected behavior, not something you can fix in your code.

When to stop fixing and start over

If you're three iterations deep on the same error with no movement, rewriting that piece from a clean prompt is nearly always faster than a fourth patch. State the goal, the field setup, and what's failing, and let it regenerate. Patching a misunderstanding just layers more code on top of it.

Or skip the whole catalog: these seven fixes (and the viz-extension ones) are baked into a free SKILL.md your coding agent reads before it writes a line, so the errors mostly never happen. Emailed to you, free.

If you'd rather not hit these at all

The other twenty fixes are in the Kit.

These seven are the dashboard-extension errors. Viz extensions have their own catalog: the encoding-token enumeration, the hidden attribute that silently loses to your own CSS, the configure dialog that opens from a gear button because the Marks-card menu won't fire it, the pane-scaling rules that stop a card looking lost at full-sheet size.

The TableauOps Extension Kit ships that full catalog as a skill file Claude Code reads before it writes a line (so the errors mostly don't happen) plus seventeen working extensions to start from. The Complete Kit adds the whole nine-lesson course for $20 more: $119 instead of $148 bought separately. And when something does break, hosting is the part you can stop thinking about.