What's actually inside a .trex
People treat the .trex as the extension. It isn't. It's a twenty-line XML file
whose entire job is to tell Tableau where the real thing lives, plus enough metadata to draw
a dialog box. Understanding those twenty lines removes most of the mystery from extension
development.
The whole file
This is a complete, valid dashboard-extension manifest. Nothing has been trimmed for the example. This is genuinely all of it:
<?xml version="1.0" encoding="utf-8"?>
<manifest manifest-version="0.1" xmlns="http://www.tableau.com/xml/extension_manifest">
<dashboard-extension id="com.yourorg.yourext" extension-version="1.0.0">
<default-locale>en_US</default-locale>
<name resource-id="name"/>
<description>What it does, in one line.</description>
<author name="Your Name" email="you@example.com"
organization="YourOrg" website="https://example.com"/>
<min-api-version>1.4</min-api-version>
<source-location>
<url>https://your-host.example.com/index.html</url>
</source-location>
<icon/>
</dashboard-extension>
<resources>
<resource id="name"><text locale="en_US">Display Name</text></resource>
</resources>
</manifest>
One highlighted line does the actual work. Everything else is labelling. When you "install an extension," Tableau parses this file, embeds a copy in the workbook, and loads that URL into an iframe. That's the entire mechanism.
Element by element
Always 0.1. It's the manifest schema's version, not yours, and it has not changed. Don't put your extension's version here: that's extension-version.
The root element, and the one that decides what kind of extension this is. A
viz extension uses <worksheet-extension> instead: different root,
different API, not interchangeable.
Its id should be reverse-domain (com.yourorg.kpicard) and
genuinely unique. Tableau uses it to tell extensions apart, so two extensions sharing an
id in one workbook is a problem you'll spend a while diagnosing.
en_US unless you're genuinely localising. It selects which <resource> block supplies the display name.
The odd one. The name isn't written here: this is a pointer to a
<resource> at the bottom of the file. Both halves must exist and the
ids must match, or the manifest fails to parse.
It exists so one extension can carry names in several languages. If you're never going to localise, it's two elements doing one element's job, and you still need both.
One line, shown in Tableau's extension dialog. Users read this while deciding whether to grant your extension data access, so it's worth more than a placeholder.
Four attributes: name, email, organization, website. Shown in the extension dialog alongside the permission prompt.
Check this before you ship. Scaffolds carry their author's details, and shipping a client an extension credited to a stranger is a small, avoidable embarrassment.
The oldest Extensions API your code needs. 1.4 is a safe floor for dashboard
extensions and runs nearly everywhere. Set it higher only when you use something newer:
it's a compatibility floor, and raising it locks out older Tableau versions for no gain.
Viz extensions need 1.11 or later, since the worksheet API arrived then.
The only line that does anything. Tableau loads this URL in an iframe and that is your extension.
It must be HTTPS with a real certificate in production: self-signed
fails. http://localhost:PORT/index.html is allowed for local development in
Desktop only, which is exactly why extensions work on your machine and break when
published.
An empty element that breaks more first loads than anything else in the file. Omit it
and some Tableau builds throw
missing elements in content model (error FD722608): the
schema lists it without the ? that marks optional elements.
It can hold a base64 PNG for a custom dialog icon. Almost nobody does this. Empty is fine: present is what matters.
The other half of <name>. Sits outside <dashboard-extension>, as a sibling. A resource-id with no matching resource is a parse error, and it's an easy one to create while renaming things.
Order is not negotiable
The schema defines a sequence, not a set. A manifest containing every required element in the wrong order fails exactly like one that's missing an element, and the message won't tell you order is the problem. Inside the root:
default-localenamedescriptionauthormin-api-versionsource-locationicon: last, immediately after</source-location>
Memorising it is a waste of effort. Keep one known-good manifest in the repo and diff against it. That single habit removes the most common first-load error permanently, and if you're generating manifests with an AI assistant, give it the template rather than trusting recall.
Keep two of them
Every extension wants two manifests that differ by exactly one line: a local one pointing at
http://localhost:PORT/index.html, and a hosted one pointing at your HTTPS URL.
Name them manifest.local.trex and manifest.hosted.trex and you'll
never again ship the localhost one by accident.
Manifests don't hot-reload.
Tableau embeds a copy of the .trex in the workbook when you add the extension.
Editing the file afterwards changes nothing. HTML and JS respond to
Reload; any manifest edit needs a full remove and re-add.
Every manifest bug on earth looks unfixed until you know this.
Where viz extensions diverge
A viz extension's manifest is the same skeleton with a different root
(<worksheet-extension>) and one addition: <encoding>
blocks after <icon/>, one per tile you want on the Marks card. Each declares
what it accepts (a measure, a dimension, a date) and which icon Tableau draws next to it.
That last part is where the time goes. The icon token must come from a fixed enumeration compiled into Tableau itself, the published documentation lists tokens the parser rejects, and the error surfaces only the first invalid one. There's a cap on how many encodings you may declare, and reusing a token across two of them throws a different error entirely at add-time rather than parse-time.
None of which is discoverable from the docs, which is a good argument for starting from a manifest that already works.
Never write one of these by hand.
ext·host generates both manifests for you: drop an extension folder or a
.zip, get back a .trex pointing at a real HTTPS URL with the
certificate handled, and flip the same extension between your dev server and hosted without
editing a line of XML. That's free.
The Extension Kit goes further: seventeen working extensions, each with both
manifests already correct, and the authoritative encoding-token list extracted from the
Tableau XSD, the one that doesn't exist in the public docs. The Complete Kit
adds the whole nine-lesson course for $20 more: $119 instead of $148 bought separately. Or
learn to write one yourself: Lesson 1 is free and ends with a real
.trex loading in your Tableau.
New pieces land as the work throws them up. Leave your email and I'll send the next one, no more than that.