An extension is just a web page
Hold the whole architecture in your head: a dashboard extension is a web page Tableau loads into a zone, plus one XML file that tells Tableau where to find it.
Why it matters
Extensions have a reputation as the 'developer corner' of Tableau, and it scares off exactly the people who would get the most out of them. The demystifying fact: Tableau renders dashboards in a Chromium browser, and an extension is nothing more than a web page loaded into one of its zones with a JavaScript bridge to the dashboard. Everything you — or your AI — already know about building web pages transfers on day one. There is no SDK to install, no build step, no framework requirement. One HTML file and one manifest is a complete, working extension.
Build me a minimal Tableau dashboard extension with no frameworks and no build step: a single index.html that loads the Extensions API library from https://tableau.github.io/extensions-api/lib/tableau.extensions.1.latest.js, calls tableau.extensions.initializeAsync() on page load, and then replaces the page heading with the dashboard's name and worksheet count read from tableau.extensions.dashboardContent.dashboard. Use a system font stack and no other styling. Explain which line is the handshake and what becomes available on the tableau.extensions object only after it resolves.
Read the file below as three moves. Load the bridge — the script tag pulls in Tableau’s Extensions API library, which puts a global tableau object on your page. Shake hands — initializeAsync() tells Tableau your page is an extension and wants the dashboard; until that promise resolves, the API is dark. Use the dashboard — after the handshake, tableau.extensions.dashboardContent.dashboard is a live object: its name, its worksheets, its zones.
That is the entire architecture. Every extension you will ever build — export buttons, write-back forms, custom filter bars — is this page with more code after the handshake.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Hello, Dashboard</title>
<!-- The bridge: Tableau's Extensions API library. -->
<script src="https://tableau.github.io/extensions-api/lib/tableau.extensions.1.latest.js"></script>
<style>
body { font-family: system-ui, sans-serif; margin: 1.25rem; }
</style>
</head>
<body>
<h1 id="status">Waiting for Tableau…</h1>
<script>
(async () => {
// The handshake. Until this resolves, tableau.extensions is empty.
await tableau.extensions.initializeAsync();
// After it: the dashboard you live in, as an object.
const dashboard = tableau.extensions.dashboardContent.dashboard;
document.getElementById("status").textContent =
'Connected to "' + dashboard.name + '" — ' +
dashboard.worksheets.length + ' worksheets';
})();
</script>
</body>
</html>Got what you came for? Mark the stop and the line fills in beneath you.