Studio, the AI builder, and the MCP publish path all run one check engine before an extension ships. Every finding it can raise is listed here, with why it matters and the fix. If you hit a check id, this is the page that explains it.
#Component contract
| Check | What it means | Fix |
|---|---|---|
src.empty |
No source. There is nothing to compile. | Write a function Component({ data, schema, theme, fields, config }) that returns markup. |
src.component-missing |
No function named Component. The runtime calls Component by name. Anything else is never invoked and the tile renders nothing. | Name the entry function exactly Component. No default export, no arrow assigned to another name. |
src.import-export |
import or export statement. The sandbox has no module loader. React 18 and d3 v7 are the only globals; an import throws at load. | Delete every import and export line. Use React and d3 directly as globals. |
src.compile |
Source does not compile. The JSX failed to transform, so nothing can render. | Read the compile error, which points at the line, and fix the syntax. |
src.viz-line-missing |
Missing the // viz: metadata line. The build reads the chart type from a // viz: comment. Without it the type is unknown. | Add a top-of-file line: // viz: {"type":"bar","variant":"grouped"} with a type from the closed list. |
src.viz-line-invalid |
The // viz: line is not valid JSON. The comment could not be parsed, so the chart type is unreadable. | Make the // viz: line a single valid JSON object with a known "type". |
#Marks-card tiles
| Check | What it means | Fix |
|---|---|---|
fields.none |
No encodings declared. A viz extension with zero tiles binds to no data and silently renders nothing. This is the most common "blank pane". | Declare 1 to 4 tiles on the // fields: line, each with id, name, accepts, and an icon. |
fields.too-many |
More than four tiles. Tableau's schema caps a viz extension at four custom encodings (maxOccurs=4). A fifth is rejected on load. | Reduce to four tiles. Fold optional roles into one "any" tile if you must. |
fields.entry-invalid |
A tile entry is malformed. One item on the // fields: line could not be parsed into a tile. | Each tile needs id, name, accepts (dimension|measure|date|any), max, and icon. |
fields.id-invalid |
Tile id has illegal characters. Encoding ids must match [a-z0-9_-]; others break the manifest. | Rename the tile id to lowercase letters, digits, hyphen, or underscore. |
fields.id-case |
Tile id is not lowercase. Tableau lowercases encoding ids; a mixed-case id will not match at runtime. | Make the id all lowercase. |
fields.id-duplicate |
Two tiles share an id. Duplicate ids collide on the Marks card; only one binds. | Give every tile a distinct id. |
fields.id-reserved |
Tile id is reserved. "detail" and "tooltip" are Tableau built-ins. A custom tile using one is indistinguishable at runtime. | Rename the tile; never use detail or tooltip as a custom encoding id. |
fields.id-builtin-name |
Tile id matches a built-in encoding name. color/size/shape/text/label/angle/path are built-in tile names; reusing one confuses the binding. | Choose an id that is not a built-in encoding name. |
fields.icon-missing |
Tile has no icon. Every Marks-card tile needs an |
Add an icon token to the tile (see the encoding icon tokens reference). |
fields.icon-unknown |
Icon token is not in the schema. Tableau rejects an |
Use a token from the encoding icon tokens reference. Note the public docs list line and detail, which the schema rejects. |
fields.icon-unverified |
Icon token is valid but unproven. The token is in the schema but has not been seen rendering in Desktop, so it may not draw. | Prefer a verified token, or test the tile in Desktop and confirm the icon shows. |
fields.icon-duplicate |
Two tiles use the same icon. Duplicate icons trigger Tableau error ED626076 and the extension fails to add. | Give each tile a distinct icon token. |
fields.max-invalid |
Tile max is not a positive integer. The per-tile field cap must be a whole number of one or more. | Set max to a positive integer (how many fields the tile accepts). |
fields.accepts-unknown |
Tile accepts an unknown role. accepts must be dimension, measure, date, or any. | Set accepts to one of the four allowed values. |
fields.name-generic |
Tile name is generic. A name like "Field" gives the user no idea what to drop there. | Name the tile for the role it plays, e.g. "Category" or "Height". |
fields.line-missing |
Missing the // fields: line. Without a fields line the build cannot know the encodings. | Add a // fields: [ ... ] line declaring the tiles. |
fields.line-invalid |
The // fields: line is not valid JSON. The tiles array could not be parsed. | Make // fields: a single valid JSON array of tile objects. |
fields.declared-unread |
A declared tile is never read. The component declares an encoding it never uses, so the user drops a field that does nothing. | Read the tile via fields/data, or remove it from the // fields: line. |
fields.read-undeclared |
Reading an encoding that is not declared. The component reads a tile id that no // fields: entry declares; it will always be empty. | Declare the tile on the // fields: line, or stop reading it. |
fields.dynamic-access |
Encoding accessed dynamically. A computed key into the encodings cannot be statically verified, so the read/declare checks cannot help. | Access encodings by literal id so the tooling can verify them. |
#Sandbox hygiene
| Check | What it means | Fix |
|---|---|---|
code.network |
Network call in the component. fetch/XHR is blocked in the extension sandbox and Tableau blocks cross-origin calls. | Render only from the data Tableau passes in. No network. |
code.storage |
localStorage or sessionStorage use. Web storage is unavailable or partitioned in the embedded browser. | Keep state in the settings (Tableau settings API) or in memory. |
code.eval |
eval or Function constructor. Dynamic code evaluation is blocked by the sandbox CSP. | Remove eval and new Function; write the logic directly. |
code.dom-access |
Direct document/DOM access. Reaching outside the React root fights React and can break on re-render. | Render through React and d3 into the component root, not document globals. |
code.parent-window |
Reaching into window.parent or top. The extension is cross-origin to the host; touching the parent throws. | Talk to the host only through the Extensions API. |
code.timers |
Uncleared timer. A setInterval/setTimeout that is never cleared keeps firing after the tile is torn down and can crash Tableau. | Clear every timer in a cleanup (return from useEffect). |
code.tableau-api |
Calling the Tableau API from a viz component. A viz extension receives its data as props; calling the dashboard Extensions API is a category error. | Read from the data/fields/schema props, not tableau.extensions. |
code.d3-select |
d3.select on a shared selector. Selecting by a non-scoped selector can grab another tile on the same dashboard. | Scope d3 selections to the component root element. |
code.date-local |
Local-time date construction. new Date(string) parses in the viewer local zone and buckets shift between users. | Bucket dates explicitly (UTC or a chosen zone), not via local Date parsing. |
code.no-viewbox |
SVG without a viewBox. A fixed-size SVG does not scale to the tile and clips or leaves whitespace. | Give the root SVG a viewBox and width/height 100%. |
code.fixed-size |
Hard-coded pixel dimensions. The tile is resized by the user; fixed pixels do not follow. | Measure the container and render responsively, or use viewBox scaling. |
code.no-empty-guard |
No empty-data guard. Before a field is dropped the data is empty; without a guard the component throws and shows a blank pane. | Return a friendly empty state when there are no rows or no required tile is bound. |
code.css-var-color |
Hard-coded color instead of the theme. A literal color ignores the theme token and looks wrong in the other mode. | Use the theme prop (palette/accent) so light and dark both work. |
#Runtime API use
| Check | What it means | Fix |
|---|---|---|
rt.enum-unknown |
Unknown API enum value. A string passed where the API expects an enum member does not match any value and throws at runtime. | Use a value from the Extensions API enums reference. |
rt.feature-guard |
Using an API feature without a version guard. A method added in a later API version throws on older Tableau builds. | Guard the call by API version, or raise the manifest min-api-version. |
#Project metadata
| Check | What it means | Fix |
|---|---|---|
proj.name |
Project name is empty. The extension needs a human name for the manifest and the splash. | Give the project a name. |
proj.ext-id |
Extension id is missing or malformed. The reverse-DNS id identifies the extension to Tableau; a bad one fails the manifest. | Use a reverse-DNS id like com.exthost. |
proj.version-semver |
Version is not semver. The manifest version must be X.Y.Z. | Set the version to three dot-separated numbers. |
proj.dev-url |
Local dev URL is not localhost http. The local manifest points at a dev server; only http on localhost is allowed there. | Point the local manifest at http://localhost: |
proj.hosted-url |
Hosted URL is not https. Tableau requires https for a hosted extension source (localhost excepted). | Serve the hosted extension over https. |
#The manifest
| Check | What it means | Fix |
|---|---|---|
trex.xml-malformed |
Manifest XML does not parse. A malformed .trex cannot be read by Tableau at all. | Fix the XML so it is well formed. |
trex.root |
Wrong root element. The manifest root must be |
Wrap the document in a single |
trex.type |
Wrong or missing manifest type. The manifest declares whether it is a dashboard or viz extension; the wrong type loads the wrong host. | Set the manifest type to dashboard-extension or viz-extension as appropriate. |
trex.name-empty |
Empty |
Fill in the |
trex.id-pattern |
Extension id fails the pattern. The |
Use a valid reverse-DNS id. |
trex.version-semver |
Manifest version is not semver. The |
Use three dot-separated numbers. |
trex.min-api-format |
min-api-version is not N.N. The minimum API version must look like 1.12. | Set min-api-version to a major.minor value. |
trex.min-api-viz-floor |
min-api-version below the viz floor. Viz extensions need at least API 1.12 (Tableau 2024.2). A worksheet default of 1.4 or 1.11 fails to add. | Set min-api-version to 1.12 or higher for a viz extension. |
trex.child-order |
Manifest children out of order. Tableau's XSD is order-sensitive; wrong order throws FD722608 (no declaration found). | Order the manifest children exactly as the schema requires. |
trex.child-missing |
A required child is missing. The manifest is incomplete and fails validation. | Add the missing required element. |
trex.child-unknown |
An unknown child element. An element the schema does not define fails validation. | Remove the element that the schema does not allow. |
trex.child-duplicate |
A child element appears too many times. An element repeated past its schema maximum is rejected. | Remove the extra occurrence. |
trex.comment-double-hyphen |
Double hyphen inside an XML comment. -- is illegal inside an XML comment and throws FD722608. | Remove the -- from the comment text. |
trex.encoding-child-order |
Encoding children out of order. The children of an |
Order the encoding children as the schema requires. |
trex.encoding-count |
Too many encodings in the manifest. More than four custom encodings is rejected (maxOccurs=4). | Reduce to four encodings. |
trex.encoding-utf16 |
Encoding name has non-ASCII bytes. Certain characters in an encoding name break the manifest parse. | Use plain ASCII for encoding names. |
trex.icon-base64 |
Icon data is not valid base64 or too large. An embedded icon over the size cap or with bad base64 fails to load. | Provide a small, valid base64 PNG (under the size cap), or use an icon token. |
trex.icon-empty |
Empty icon element. An empty |
Remove the empty icon element or fill it. |
trex.author-attrs |
Author element missing attributes. The |
Fill in the author attributes. |
trex.author-website-https |
Author website is not https. The author website URL must be https. | Use an https URL for the author website. |
trex.url-count |
Wrong number of source-location URLs. The manifest expects exactly one |
Provide a single source-location URL. |
trex.url-scheme |
Source URL has the wrong scheme. Hosted needs https; local needs http on localhost. | Fix the source-location URL scheme. |
trex.permissions-value |
Unknown permission value. A |
Use a permission value the schema defines (e.g. full data). |
trex.context-menu |
Context-menu element problem. A malformed context-menu block fails validation. | Fix or remove the context-menu element. |
trex.resources-nested |
Place |
|
trex.resource-ref-missing |
A referenced resource is missing. The manifest points at a resource that is not declared. | Declare the resource or drop the reference. |
#Manifest-changing edits
| Check | What it means | Fix |
|---|---|---|
diff.tiles-changed |
The encodings changed since the last save. Changing tiles changes the manifest, so an already-installed copy must be re-added, not reloaded. | After a tiles change, remove and re-add the extension in Tableau (a reload keeps the old manifest). |
diff.tiles-removed |
A tile was removed. Removing an encoding is a breaking manifest change for anyone who bound a field to it. | Expect installed copies to lose that binding; re-add the extension. |