# Tableau Extensions API Core > Installation, API overview, debugging, and security. Shared setup and security for every extension type. Product-specific guides are in the other domain files. ## Tableau Extensions API The Tableau Extensions API allows developers to create dashboard extensions and viz extensions for Tableau. Tableau extensions are web applications that can interact and communicate with Tableau. A dashboard extension can be placed in the dashboard like any other dashboard object. A Tableau viz extension creates new viz types that Tableau users can access through the worksheet Marks card. :::note If you are looking for information about how to extend Tableau calculations to include popular data science programming languages and external tools, see the [Tableau Analytics Extensions API](https://tableau.github.io/analytics-extensions-api/). ::: The Tableau Extensions API is a JavaScript library that is organized into namespaces that contain the classes and methods for communicating with Tableau components. The Tableau Extensions API library supports both dashboard extensions and viz extensions, although not all interfaces and methods apply to both extensions. For an overview of how the API is organized, see [Tableau Extensions API Basics](./trex_api_about.md). Details on the supported platforms and hardware requirements can be found in the [Installation instructions](installation.md). ### Start here * [Installation](installation.md) * [Get Started with Viz Extensions](./vizext/trex_viz_getstarted.md) * [Get Started with Dashboard Extensions](./dashext/trex_getstarted.md) --- ## Installation This section will take you through the process of setting up your environment to use one of the sample dashboard or viz extensions. Using one of the sample extensions is a great way to learn and great way to get started developing your own extensions. In this section, you will start a simple web server on your computer to host the samples. You can use the same process for hosting the extension when you start developing your own. --- :::info **What's in a Tableau extension?** A Tableau extension consists of an XML manifest file (`.trex`), an web page (`.html`) that uses a Tableau-provided JavaScript library, and the JavaScript file (`.js`) (or files) that contain your extension logic. The Tableau dashboard and viz extensions are supported on Tableau Server, Tableau Cloud, and Tableau Desktop. ::: --- #### What you need to get started If you want to create an extension or work with the sample code, make sure you have the following dependencies installed: * [Git](https://git-scm.com/downloads) * [Node.js and npm](https://nodejs.org/en/download/) You need Node.js and npm to run the viz and dashboard extension demos. Node.js is a JavaScript runtime. npm is a package manager for Node.js and is installed when you install Node.js. --- #### Get the Tableau Extensions API SDK You can get the Tableau Extensions API SDK in two ways. Clone the repository if you want to contribute to the open source project or keep current with the latest changes. Download the `.zip` file if you want to view the samples and work on your own. * Open a terminal in the directory where you want to copy the Tableau Extensions SDK. Then run the following command to clone the Tableau Extensions API git repository: HTTPS git clone https://github.com/tableau/extensions-api.git * Open a terminal in the directory where you want to copy the Tableau Extensions SDK. Then run the following command to clone the Tableau Extensions API git repository: SSH `git clone git@github.com:tableau/extensions-api.git` * Download the [Tableau Extensions API SDK (.zip file)](https://github.com/tableau/extensions-api/archive/main.zip) and extract the files to your computer. --- #### Requirements for using extensions in Tableau To use Tableau dashboard extensions, viz extensions, or data apps in Tableau, you need one of the following versions of Tableau. The data apps beta is available only on Tableau Cloud. For dashboard extensions: * Tableau Desktop 2018.2 and later * Tableau Server 2018.2 and later * Tableau Cloud For viz extensions: * Tableau Desktop 2024.1 and later * Tableau Server 2024.1 and later * Tableau Cloud For data apps (Cloud-only Beta): * Tableau Cloud #### Security requirements To run extensions on Tableau Server or Tableau Cloud, support for extensions must be enabled, and depending upon the extension and the data access it requires, the extension might need to be added to the safe list for the site. See [Manage Dashboard Extensions on Tableau Server](https://onlinehelp.tableau.com/current/server/en-us/dashboard_extensions_server.htm) or [Manage Dashboard Extensions on Tableau Cloud](https://onlinehelp.tableau.com/current/online/en-us/dashboard_extensions_server.htm) for more information. Special care must be taken for data apps, available only on Tableau Cloud during the beta, to ensure that security concerns are addressed. See [Manage data apps](./dataapps/trex_dataapp_manage.md) and [data app security](./dataapps/trex_dataapp_security.md) for more information. --- ## Tableau Extensions API Basics The Tableau Extensions API is organized by namespaces. The type of extension you create determines the set of namespaces the extension will have access to. | Extension | Description| | ----- | ------- | | Dashboard extensions | For example, if you create and register a dashboard extension, the extension will have access to the `dashboardContent` namespace, which provides access to the dashboard object. When you have the dashboard object, you have access to all elements in the dashboard, including the worksheets, marks, filters, parameters, and data sources. | | Viz extensions | If you create and register a viz extension, the extension will have access to the `worksheetContent` namespace, which provides access to the worksheet object. When you have the worksheet object, you have access to all elements in the worksheet, including the marks, filters, parameters, and data sources.| | [Data apps (Cloud-only Beta)](./dataapps/trex_dataapp_overview.md) | Data apps are available only on Tableau Cloud during the beta. They combine a custom web interface with Tableau hosting and runtime access to Tableau data. The app's Dashboard or Viz Extension registration determines access to `dashboardContent` or `worksheetContent`. In supported environments, the `workbook` namespace provides access to workbook data sources, which the app can query using `queryAsync()`. See [Get Started with data apps](./dataapps/trex_dataapp_getstarted.md) for setup guidance. | ### Methods added for Data Apps The data apps beta is available only on Tableau Cloud. Three methods added to the Extensions API for this release support data apps: | Method | Purpose | | --- | --- | | `queryAsync()` | Gets data from the configured data source asynchronously. | | `readMetadata()` | Gets a data source's metadata. | | `fetch()` | Makes a curated set of Tableau REST API requests, subject to the site's allowed API access and the viewing user's permissions. | See [Manage data apps](./dataapps/trex_dataapp_manage.md) and [data app security](./dataapps/trex_dataapp_security.md) for the controls that apply to these requests. ### Navigating the top-level `tableau` and `extensions` namespaces The [Extensions API Reference](pathname:///api) namespaces are like containers that comprise the classes and methods for communicating with Tableau components. At the highest-level, is the `tableau` namespace, which has no constructs, but contains the `extensions` namespace. The `tableau` namespace serves primarily as the overall container and keeps the global namespace clean. The `extensions` namespace is the namespace for Tableau extensions. A dashboard extension is one type of extension. When a extension is registered as a dashboard extension, it has access to the `dashboardContent` namespace, and all of the objects and classes of the dashboard. A viz extension is another type of extension. When a extension is registered as a viz extension, it has access to the `worksheetContent` namespace, and all of the objects and classes of the worksheet. A viz extension does not have access to the `dashboardContent`. The type of extension you have registered determines what namespaces will be available. Some namespaces, like the `settings`, `environment`, and `ui` are available to all extensions. Call `tableau.extensions.initializeAsync()` to initialize the extension before using the Extensions API. When this method is called, it also triggers Tableau to configure the Extensions API. Like the Tableau JavaScript API, the Extensions API follows the [CommonJS Promises/A standard](http://wiki.commonjs.org/wiki/Promises/A) for asynchronous method calls. ```mermaid classDiagram direction RL namespace tableau { class extensions class dashboardContent class worksheetContent class environment class settings class ui } class extensions extensions ..> dashboardContent extensions ..> worksheetContent extensions ..> environment extensions ..> settings extensions ..> ui extensions: initializeAsync() ``` ### Registering and accessing dashboard extensions The dashboard extension is one type of extension in the Tableau extensions namespace (and it is accessed using `tableau.extensions`). To register the extension, you declare the type of extension in the manifest file (`.trex`). For more information about what goes in the file, see [Tableau Manifest File](./dashext/trex_manifest.md). ```xml ``` After the extension is initialized, it provides access to the objects in the dashboard, but also has access to the namespaces that are common to all extensions. For example, you can use the `tableau.extensions.environment` to query the environment the dashboard is running in, or `tableau.extensions.settings` to set or get key-value pairs associated with a specific instance of the extension. That is, an extension that has been added to one dashboard can have different settings from another instance of the extension in another dashboard. The `tableau.extensions.settings` can be saved with the workbook, so you can configure the dashboard and the extension in specific ways and then share that configuration with others. To access the objects in the dashboard, you specify the namespace reserved for dashboard extensions `dashboardContent`, which then gives you access to the dashboard object. For example, the following code snippet gets the array of worksheets in the dashboard. ```javascript window.onload = tableau.extensions.initializeAsync().then(async () => { // Get the array of worksheets in the dashboard const worksheets = tableau.extensions.dashboardContent.dashboard.worksheets; // ... }); ``` ### Registering and accessing viz extensions The viz extension is another type of extension in the Tableau extensions namespace (and it is accessed using `tableau.extensions`). To register the viz extension, you specify the extension as a `worksheet-extension` in the manifest file (`.trex`). For more information about what goes in the file, see [Tableau Viz Manifest File](./vizext/trex_viz_manifest.md). ```xml ``` After the viz extension is initialized, it provides access to the objects in the worksheet, but also has access to the namespaces that are common to all extensions. For example, you can use the `tableau.extensions.environment` to query the environment the extension is running in, or `tableau.extensions.settings` to set or get key-value pairs associated with the extension. That is, an extension that has been added to one worksheet can have different settings from another instance of the extension in another worksheet. The `tableau.extensions.settings` can be saved with the workbook, so you can configure the viz extension in specific ways and then share that configuration with others. To access the properties and methods in the worksheet, you specify the namespace used by viz extensions, `worksheetContent`, which then gives you access to the worksheet object. For example, the following code snippet gets the worksheet in the dashboard. ```javascript window.onload = tableau.extensions.initializeAsync().then(async () => { // Get the worksheet that the Viz Extension is running in const worksheet = tableau.extensions.worksheetContent.worksheet; // ... }); ``` ### Using properties and methods in the `dashboard` and `worksheet` namespace The Tableau Extensions API is similar to the Tableau Embedding API. The `dashboard` and `worksheet` class or namespace inherit from an abstract `sheet` class. You can use the [Extensions API Reference](pathname:///api/) to find the properties and methods that are available for dashboard and worksheet objects. For example, after you access a worksheet object, you can use that worksheet to get properties. such as the name or size, or to call methods that get or apply filters, get data, or set or remove event listeners. The following code fragment shows an example of setting an event listener `addEventListener` on a worksheet. ```javascript // Add an event listener for the selection changed event on this sheet. // Assigning the event type to a variable just to make the example fit on the page here. const markSelection = tableau.TableauEventType.MarkSelectionChanged; let unregisterEventHandlerFunction = worksheet.addEventListener( markSelection, myfunctionHandleSelectionEvent); ``` The following diagram shows the `tableau` namespace. ```mermaid classDiagram namespace tableau { class extensions class dashboardContent class worksheetContent class worksheet class dashboard class environment class settings class ui class worksheets } class extensions extensions ..> dashboardContent extensions ..> worksheetContent extensions ..> environment extensions ..> settings extensions ..> ui extensions: initializeAsync() class worksheetContent worksheetContent ..> worksheet class dashboardContent dashboardContent ..> dashboard dashboard worksheets class dashboard { name sheetType object worksheets } dashboard ..> worksheets worksheets ..> worksheet class worksheets class worksheet ``` --- ## Debugging and Troubleshooting --- ## Debug Extensions in Tableau Server and Tableau Cloud Dashboard and viz extension are web apps that run inside of a dashboard or a worksheet on Tableau Server and Tableau Cloud. If you add an extension in web authoring, or publish a dashboard or worksheet that contains an extension to Tableau Server or Tableau Cloud, you can debug the extension using the debugging tools that are built into the browser that you are using. The following section describes how you could debug your extension using Chrome. The same principles generally apply if you are using other browsers and their developer tools. For more information about the Chrome developer tools, see [Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools/). For more information about debugging JavaScript with the DevTools, see [Debug JavaScript](https://developers.google.com/web/tools/chrome-devtools/javascript/). :::note For Tableau Desktop, see [Debugging Extensions in Tableau Desktop](./trex_debugging.md). ::: --- ### Open the debugging tools in the browser 1. Open the workbook containing the extension in Tableau Server or Tableau Cloud. :::note If the extension fails to load and you are using `localhost` to serve your extension, you might encounter the mixed content security setting. To get past this issue, see [Load and view localhost content on sites that use secure connections](#load-and-view-localhost-content-on-sites-that-use-secure-connections). Make sure that the extension is on the Allow list (safe list) for Tableau Server or Tableau Cloud. See [Add extensions to the safe list and configure user prompts](https://help.tableau.com/current/online/en-us/dashboard_extensions_server.htm#add-extensions-to-the-safe-list-and-configure-user-prompts) (Tableau Cloud) or [Add extensions to the safe list and configure user prompts](https://help.tableau.com/current/server/en-us/dashboard_extensions_server.htm#add-extensions-to-the-safe-list-and-configure-user-prompts) (Tableau Server). ::: 2. Open the debugging tools. The process varies by browser. If you are using Chrome, enter **CTRL** + **SHIFT** + **i** (Windows) or **COMMAND** + **OPTION** + **i** (MacOS). 3. Locate the source code for your extension. For example, if you were using Chrome for debugging, you can open the Source pane. Under folders shown on the navigation pane, you will find one for the extension. For example, you might see something like `extension_frame_37 (filtering.html)`. Under this, you will find the name of the server (for example `localhost`), and you can drill down to your JavaScript source files. --- ### Set breakpoints and explore the code If you need to debug your extension, setting a breakpoint in your source code is a good way to get started. When the breakpoint is hit, the code execution is paused in the debugger. You can then use the debugger controls to step through your code. You can use the debugging tools to see the values of local variables, and the call stack. You can also hover over in-scope variables in source window and see the current values. 1. Locate the line in your source code and set the breakpoint. For example, you want to set this early in your code after the `initializeAsync()` function call. 2. Refresh or reload the browser window. You might see the permission dialog box prompt as the extension gets loaded. 3. Step through your code or set other breakpoints. You can examine variables to see what information the extension has access to. For example, if you step or stop on the source line where you have access to the dashboard or worksheet object, you can examine the values of the available resources. ```javascript // To get filter info, first get the dashboard. const dashboard = tableau.extensions.dashboardContent.dashboard; ``` Or worksheet, in the case of viz extensions. ```javascript // Get the worksheet. const worksheet = tableau.extensions.worksheetContent.worksheet; ``` 4. Explore the dashboard or viz extension namespace using the Console window. For example, entering the following in the Console window (while you are paused in your extension code) will print out the names of all the worksheets in the dashboard. ```javascript tableau.extensions.dashboardContent.dashboard.worksheets.forEach(function (worksheet){console.log(worksheet.name)}) ``` The following screenshot illustrates what your debugging session might look like if you use the Chrome DevTools. A breakpoint was set and the extension's JavaScript code is paused in the debugger. ![alt text](../assets/server_dbg_chrome.png "Chrome DevTools showing a the debugger paused on an extension breakpoint") --- ### Debugging during initialization If you need to troubleshoot or debug issues that prevent your extension from loading or initializing on Tableau Server or Tableau Cloud, you can set breakpoints that trigger when your JavaScript code is loaded. 1. In the Chrome browser, select **Event Listener Breakpoint > Script > Script First Statement**. After you select the event breakpoint and refresh or reload the browser window, code execution will stop when every script is loaded. 2. Reload or refresh the browser. 3. In the debugger, click **Continue** to get to your JavaScript code. You might need to click **Continue** many times, as code execution stops for all the scripts in the dashboard. If you are just looking to debug general initialization problems and haven't tested your extension in Tableau Desktop, you might want to start there first. In Tableau Desktop, there is a debugging option you can set to pause the extension when it is loading. For more information, see [Debugging loading and initialization issues](./trex_debugging.md#debugging-loading-and-initialization-issues). If your extension fails to load at all on Tableau Server or Tableau Cloud, check the console window of the debugger to see if there is an error message of some kind. The console messages can give you clues about where to begin investigating when something goes wrong. If the error is caused by mixed content (trying to load an HTTP web page inside of a secure HTTPS server), see [Load and view localhost content on sites that use secure connections](#load-and-view-localhost-content-on-sites-that-use-secure-connections). --- ### Load and view localhost content on sites that use secure connections *Mixed Content Warning* If you want to test and debug your extension (running on `http://localhost`) with Tableau Cloud, or with a Tableau Server site that is using HTTPS, you might find that your extension fails to load. You won't see any error message in the dashboard if this happens. There will just be an empty `iframe` where the extension should appear. The reason for this is that the default settings of many browsers blocks the extension from loading because the extension is not using a secure connection (HTTPS). To temporarily get around these safety settings for the session, you can click the shield icon (or lock icon) in the browser's address bar. The alert dialog box will allow you to either load the scripts, or allow you to view the full content of the page. As soon as you load the unsafe scripts or allow the blocked content, the extension will load and will continue to be available for the duration of your session. Be sure to close the browser completely when you are finished testing. The following example shows what you might see in Chrome. ![alt text](../assets/online_blocked_extension.png "Chrome browser showing alert when extension running on a localhost server") During the session, anytime you refresh or reload the web page, you will see the extensions dialog box requesting permission to run. And in the debugger console, you might see a warning message about mixed content. ```cli Mixed Content: The page at 'https:/some_URLs#4' was loaded over HTTPS, but requested an insecure resource 'http://localhost:8765/Samples/Dashboard/Filtering/filtering.html'. This content should also be served over HTTPS. ``` --- ## Debug Extensions in Tableau Desktop A dashboard extension embeds a web page and runs a Chromium-based browser inside Tableau Desktop. Fortunately, you can debug this embedded web browser by using the remote debugging abilities built into Chromium. Debugging an extension involves starting Tableau with a command option to enable remote debugging (over a debug port). You can then run a specific version of the Chromium or Chrome debugger and connect to Tableau Desktop by using the debug port. :::note For Tableau Server or Tableau Cloud, see [Debugging Extensions in Tableau Server and Cloud](./trex_debug_server.md). ::: ### Download the Chromium Browser To debug your extension, you must use a Chromium-based browser (Chromium is the open-source version of Chrome). In most cases you can use Chrome, but if you encounter browser incompatibilities, we recommend that you use specific builds of Chromium. Use the version of Chromium that matches the version of the browser running inside Tableau. Download and unzip the file (`chrome-mac.zip`, `chrome-win.zip`) and run `chrome.exe` (Windows) or `chromium` (macOS). #### Design for compatibility For the best user experience, design your extension to work with the version of the Chromium that matches the version of Tableau that your customers use. Often this means that you must cover multiple versions of Tableau, so we encourage testing for version compatibility, and carefully coordinating which versions to design for. To ensure consistency of extensions and other web zone functions, the Chromium packaged with Tableau is maintained with an earlier version. The packaged browser is regularly patched to address Common Vulnerabilities and Exposures (CVE). Please refer to security version listed [QtWebEngine/ChromiumVersions](https://wiki.qt.io/QtWebEngine/ChromiumVersions). Tableau Desktop version | Chromium version | Chrome Version |----|----|----| 2018.2, 2018.3 | 47.0.2526.0 | Not available 2019.1 and later | 79.0.3945.0 | Chrome version 79 or earlier. Latest maintenance release of 2020.2.7+, 2020.3.6+, 2020.4.2+ | 87.0.4280 | Chrome version 80 or later. 2021.1 and later | 87.0.4280 | Chrome version 80 or later. 2025.2 and later | 108.0.5359.220 | Chrome 108 or later. **Chromium downloads for debugging Tableau 2018.2, 2018.3** * [Chromium for Windows (47.0.2526.0)](https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Win%2F352221%2Fchrome-win32.zip?generation=1443839123039000&alt=media) * [Chromium for macOS (47.0.2526.0)](https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Mac%2F352221%2Fchrome-mac.zip?generation=1443838516381000&alt=media) **Chromium downloads for debugging Tableau 2019.1 and later** * [Chromium for Windows (`chrome-win.zip`) (79.0.3945.0)](https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Win_x64/706915/) * [Chromium for macOS (`chrome-mac.zip`) (79.0.3945.0)](https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Mac/706915/) Note If you're using Tableau 2021.1, or the latest maintenance releases of Tableau 2020.2.2.7+, 2020.3.3.6+, and 2020.4.2+, you can use Chrome version 80 (or later) for debugging your extension. #### About Chromium versions Finding specific versions of Chromium involves some investigative work. Google doesn't provide an archive of old versions. You must first find the Base Branch Position (a specific commit) that matches the release version you are looking for from [Chromiumdash](https://chromiumdash.appspot.com/releases). Using the Base Branch Position, download the binary from the [Chromium snapshot storage](https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html). If you can't find the exact match for the Base Branch Position, choose one that is close to it. Chromium and Chrome releases share the same major version number. For example, Chrome version 108 is built from Chromium version 108. Use a version of Chromium that matches the major version and the minor version (as close as possible) to the one you're looking for. --- ### Start Tableau Desktop and enable debugging (Windows) 1. Exit Tableau if it's running on your computer. 2. Open a Command Prompt window. 2. Start Tableau using the following command. Replace `` with the version of Tableau you are using (for example, `Tableau 2025.2`). ```cli "C:\Program Files\Tableau\Tableau \bin\tableau.exe" --webEngineArgs --remote-debugging-port=8696 ``` This command enables remote debugging of extensions for this session of Tableau. :::note The remote debugging port (for example, `8696`) must match the port address you use with Chromium for debugging. This is *not* the HTTP port that you use to host your extension, the port that is specified in the manifest file (`.trex`). ::: :::important If using the `QTWEBENGINE_REMOTE_DEBUGGING` environment variable to enable remote debugging, **unset it when done**. Leaving this variable set allows unauthenticated remote access to your extension’s runtime, creating a remote code execution vulnerability. ::: --- ##### Optional - Create a Tableau shortcut for debugging (Windows) For convenience, you could also create a shortcut for Tableau and add the remote debugging option to Properties dialog box. 1. Select Tableau from the **Start** menu, and then right-click and select **Properties** or select **Open file location**. If you open the file location, you can create a new shortcut to `Tableau.exe` (call it something like *Tableau - Debug* ). Right click the shortcut to open the Properties dialog box. 3. In the Properties dialog box, append `--webEngineArgs -remote-debugging-port=8696` at the end of the command in the **Target** text box. The debugging option goes after the closing quotation mark for `"Tableau.exe"`. ![Debug Shortcut](../assets/Tableau_shortcut_debug2.png) --- ### Start Tableau Desktop and enable debugging (macOS) 1. Open a Terminal window. 2. Start Tableau using the following command. Replace `` with the version of Tableau you are using (for example,`2025.3.app`). ```cli open /Applications/Tableau\ Desktop\ .app --args --webEngineArgs --remote-debugging-port=8696 ``` This command enables remote debugging of extensions for this session of Tableau. :::note The remote debugging port (for example, `8696`) must match the port address you use with Chromium for debugging. This is *not* the HTTP port that you are using to host your extension, the port that is specified in the manifest file (`.trex`). ::: :::important If using the `QTWEBENGINE_REMOTE_DEBUGGING` environment variable to enable remote debugging, **unset it when done**. Leaving this variable set allows unauthenticated remote access to your extension’s runtime, creating a remote code execution vulnerability. ::: --- ### Debugging Tableau Desktop using Chrome/Chromium After you install the Chrome (or Chromium) browser and enable debugging in Tableau, you can start debugging your extension. 1. Open the dashboard with the extension you want to debug in Tableau. 2. Start Chromium and set the URL to [`http://localhost:8696`](http://localhost:8696) This will bring up the page selector UI. The port (for example, `8696`) must match the port address you specified as the remote debugging port when you started Tableau. 3. Pick the extension you want to debug from this page, and debug just like you would any other web application (set breakpoints, step through the code, and examine variables). Note that the name of the extension is based on the title of the web page that was loaded for the extension. If the extension hasn't loaded yet the name might be blank. See [Debugging loading and initialization issues](#debugging-loading-and-initialization-issues). ![Remote Debugging](../assets/remote_debugging.gif) Note that you can only debug one extension, or instance of an extension, at a time. However, you can open a new Chromium tab or window to debug additional extensions. --- ### Debugging loading and initialization issues If you must troubleshoot or debug issues that prevent your extension from loading or initializing, you can set breakpoints that trigger when your JavaScript code is loaded. #### For Tableau 2021.1 and later 1. Start the debugging session as described in [Debugging Tableau Desktop using Chrome/Chromium](#debugging-tableau-desktop-using-chromechromium). 1. Click the **Sources** tab in Chrome/Chromium, under **Event Listener Breakpoints**, click **Script** and enable the **Script First Statement** breakpoint. You just have to do this one time. 1. In Tableau Desktop, select the extension in the dashboard and click **Reload** from the **More Options** shortcut menu. The debugger will pause each time the first statement of a script runs, allowing you to debug the startup process. 1. To get to your JavaScript code, click **Continue** several times. After your JavaScript is loaded, you can set a breakpoint in your startup code. --- #### For Tableau 2020.4 and earlier It can be difficult to hit breakpoints that occur during the loading of your page because of the remote debugging process, and because of the way loading was handled prior to Tableau 2021.1. To help with this, you can select a menu option that causes your extension to wait to load until you trigger it to proceed. 1. Select the extension in the dashboard and select **Debug Options** > **Pause Before Loading** from the shortcut menu. 2. Reload your extension. Select **Reload** from the shortcut menu. 3. In Chromium, go to the debugging homepage ([http://localhost:8696](http://localhost:8696)). 4. Click the *second item* listed to attach to the browser instance of your extension. **Note:** When you click the list item, it will be completely blank, but it is really there. The cursor changes so you can select it. 5. Click the **Sources** tab in Chromium, under **Event Listener Breakpoints**, click **Script** and enable the **Script First Statement** breakpoint. You just have to do this one time. 6. In Tableau Desktop, click the extension zone to load your page. The debugger will pause each time the first statement of a script runs, allowing you to debug the startup process. 7. To get to your JavaScript code, click **Continue** several times. After your JavaScript is loaded, you can set a breakpoint in your startup code. ![Startup Debugging](../assets/onload_debugging.gif) --- ### Known issues with debugging extensions in Tableau Desktop --- ##### Reload closes debugging connection (2020.1 and earlier) While you are debugging your extension, there are times you might want to reload or refresh your web page to execute and debug different parts of your code. However, when you click **Reload** from the shortcut menu to reload your extension, the remote debugger loses the connection with the extension. The reason for this is that **Reload** option tears down and re-creates the browser control, which means you'll need to establish a new debugging session. Note that you don't need to close and reopen the Chromium browser every time you click **Reload**. You can start another debugging session by entering the URL (`http://localhost:8696`) in the Chromium address bar and selecting the extension from the page selector. For more information, see [What Happens When You Reload an Extension?](./trex_reload.md). --- ## Error Codes for Extensions Errors that are returned from the Extensions API are custom Tableau error objects that extends the standard JavaScript error object. ### Using Extensions API error codes The Extensions API wraps the standard error object with an `errorCode` property. You can use this error code for debugging or troubleshooting your extension. Any time you encounter an error when you are running your extension, you can look at this `errorCode` to determine the cause. See [Error Codes](pathname:///api/enums/ErrorCodes.html). As you create your extension, you want to be sure to catch potential error conditions. For example, you should validate user input and make use of `try` ... `catch` statements. The Extensions API makes use of JavaScript promises. You can use the `.catch` method to field the errors that could be returned in the promise and any subsequent `.then` methods. ### Handle extensions.ui dialog box errors For an example of how to handle an extension dialog box error, see the [UINamespace](https://github.com/tableau/extensions-api/tree/master/Samples/Dashhboard/UINamespace) sample. The sample shows how you could handle the error condition that occurs if a user dismisses a modal dialog box (`DialogClosedByUser`). In this extension, the user is expected to click the **Start Auto Refresh** button, which saves the configuration settings and closes the dialog box, by calling the `tableau.extensions.ui.closeDialog()` method with the return payload. If a user clicks the dialog box control (the **X** in the upper-right corner) instead, the error occurs. The following snippet illustrates how you could handle this error: ```javascript tableau.extensions.ui.displayDialogAsync(args... ).then((args... ) => { // // code that sets up the extension in the modal dialog box // }).catch((error) => { // One expected error condition is when the popup is closed by the user // (meaning the user clicks the 'X' in the top right of the dialog). // This can be checked for with: switch(error.errorCode) { case tableau.ErrorCodes.DialogClosedByUser: console.log("Dialog was closed by user"); break; default: console.error(error.message); } }); ``` --- ## Use Log files to Troubleshoot Dashboard and Viz Extensions You can use the Tableau Desktop and Tableau Server log files to troubleshoot issues with your dashboard extension. The log files can help you identify and fix registration errors, and can help you track extension activity. You can use any text viewer, or tools like the Tableau Log Viewer, to filter the log files, so you can focus in on the activities of concern. Support for extensions is built-in to Tableau, so there is nothing you need to set up and no code you need to add to view extension activity in Tableau log files. ---- ### Locate the Tableau log files By default, the Tableau Desktop log file (`log.txt`) is are stored in the following location: `Users//Documents/My Tableau Repository` The `log.txt` file contains information about dashboard extension registration and information about extension command execution. ### Download and install the Tableau Log Viewer The Tableau Log Viewer is an open source tool that makes it easier to read Tableau log files. You can install the Log Viewer by downloading a `.zip` file for either Windows or MacOS. If you want to build the Tableau Log Viewer yourself, you can also download or clone the Log Viewer repository. 1. Download the latest release in the [Releases Section](https://github.com/tableau/tableau-log-viewer/releases) of the [Tableau Log Viewer](https://github.com/tableau/tableau-log-viewer) repository on GitHub. 2. Extract `.zip` file on your local computer. Launch the Log Viewer application (`Log Viewer.exe` on Windows, or `Log Viewer.app` on MacOS). 3. Open the log file from the File menu, or drag and drop the Tableau log file into the Log Viewer window. The Tableau Log Viewer supports both Tableau Desktop and (most) Tableau Server log files. For more information, see the wiki pages on GitHub for [Tableau Log Viewer](https://github.com/tableau/tableau-log-viewer). ### Find dashboard extension events The Desktop Tableau log file (`log.txt`) is a JSON formatted file. The extensions activity is tagged with the key-value pair `"k":"extension"`. You can search the log for `extension`. For example, this entry shows successful registration. ```json {"ts":"2017-11-27T17:55:55.665", pid":12100,"tid":"3d08","sev":"info","req":"-", "sess":"-", "site":"{FA6345B5-D64A-4ADB-9435-12F170D9B2AB}", "user":"-", "k":"extension", "v":{"Event":"Extension Successfully Registered", "id":"com.tableau.extensions.samples.datasources", "name":"DataSources Sample"} } ``` Although, you can use any file viewer or text editor, the Tableau Log Viewer makes finding the events easy to view and navigate. ![](../assets/log_viewer.png) ### Troubleshooting errors that occur during registration The extension registration process occurs during Tableau start up. This is when Tableau looks in the `Extensions` folder for the manifest files (`.trex`) files, and then parses the files to check for schema violations, or to check that there are duplicate identifiers. If the manifest file checks out, Tableau writes the event to the log file and the extension appears in the list of available extension in the dashboard. There are two situations that can occur during the extension registration process that might cause errors. - Duplicate extension ids - - XSD Validation errors #### Duplicate extension identifers Each dashboard extension must have a unique identifier. The identifier is specified in the manifest file. ```xml ``` Tableau uses the extension identifier and the extension version number to register the dashboard extension. At startup, Tableau checks that there are no duplicate identifiers. If an extension is found that has the same identifier as one that is already registered, an error is logged and the extension with the duplicate identifier is not registered and does not appear in the list of available extensions. ```cli Error: Registration Failed: Multiple Extensions With ID, com.tableau.extensions.samples.settings, Found file: Settings.trex id: com.tableau.extensions.samples.settings ``` #### XSD Validation errors The extensions manifest file (`.trex`) is an XML-based file. As part of the registration process, the `.trex` file validated against the XML schema definition file (XSD). If there are schema violations with the `.trex` file, the extension cannot be registered and XSD errors are written to the Tableau log file. The first error specifies the type of error and the name of the manifest file. ```xml Error: Registration Failed: XSD Validation Failed file: Sample.trex ``` Each XSD validation error in the file is logged separately, which makes it easier to locate and fix problems. For example, the following error shows the exact line number and location where the error occurred. In this case, the value for `extension-version` number was left blank `""`. ```cli Error: Error(2,86): value '' does not match regular expression facet '[0-9]\.[0-9]' (id: C:\Users\me\Documents\My Tableau Repository (Beta)\Extensions\Sample.trex) file: Sample.trex ``` ---- ### Track extension activity In addition to troubleshooting errors during registration, you can track extension activity. There are other types of events that are reported with the `extension` keyword, such as successful registration, and command execution. The following section walks through the events that occur when the extension is successfully registered, and then placed on the dashboard. #### Successful registration When no errors are found with the manifest file, Tableau completes the registration process and writes `Extension Successfully Registered` to the log file. At this point, the extension is available for use. ```cli Event: Extension Successfully Registered id: com.tableau.extensions.dashboard.tutorial.final name: Tutorial - Complete ``` You can use the `id` of the extension, in this case `com.tableau.extensions.dashboard.tutorial.final` to search for instances where the extension is used. #### Drop-on-dashboard When you drag an extension on to the dashboard a series of events take place. Using the id of the extension, you can find the sequence of `command-pre` and `command-post` tabdoc events that place the extension in the dashboard. ```cli args: tabdoc:drop-on-dashboard add-as-floating="false" dashboard="Overview" drop-location={"x": 36,"y": 259} is-horizontal="false" zone-param="com.tableau.extensions.dashboard.tutorial.final" zone-type="add-in" name: tabdoc:drop-on-dashboard ``` #### Extension initialization After an extension has been placed in a dashboard, an initialization sequence begins. ```cli Event: Executing Command: initialize-add-in-instance id: com.tableau.extensions.dashboard.tutorial.final instance-id: com.tableau.extensions.dashboard.tutorial.final name: Tutorial - Complete ``` A key part of this is that the extension is assigned an `add-in-instance-id`. This instance id is something you can use to further track activity. ```cli args: tabdoc:initialize-add-in-instance add-in-locator-pres-model={"add-in-instance-id": "36C4012514F042DB8F2AB466B445726A","sheet-path":{"sheet-name": "Overview","is-dashboard": true}} name: tabdoc:initialize-add-in-instance ``` #### Executing Save-Settings In this example, the extension we are following calls `tableau.extensions.settings.saveAsync()` function when a selection has been made. ```cli Event: Executing Command: save-add-in-settings id: com.tableau.extensions.dashboard.tutorial.final instance-id: com.tableau.extensions.dashboard.tutorial.final name: Tutorial - Complete ``` In this case, the selection was the name of a worksheet ("Sale Map"). ```cli args: tabdoc:save-add-in-settings add-in-locator-pres-model={"add-in-instance-id": "36C4012514F042DB8F2AB466B445726A","sheet-path":{"sheet-name": "Overview","is-dashboard": true}} add-in-settings={"sheet":"Sale Map"} name: tabdoc:save-add-in-settings ``` You can use the information in the log file to help you track activity, and to help you troubleshoot problems with extension registration. ---- ### List of extension events | Error | Description | | --- | --- | | Duplication error | Manifests with the same id. The id and file name are logged. | XSD Validation error | Details of the errors are logged in separate messages. The file name is logged | Info | Description | | --- | --- | | Registration success | The id and name of the extension. | Command Execution | Logs when and what command the extension is executing. The Id, instance-Id, and extension name are logged as well. --- ## What Happens When you Reload an Extension? When you create an dashboard extension or viz extension, you are often making frequent changes to the JavaScript code, the HTML pages, or the manifest file. It is helpful to see the results of those changes while you are working. Because dashboard and viz extensions are essentially web applications, you can do most of this quickly, without jumping through the hoops of building or compiling your code. However, because the extension is running inside a Tableau dashboard or worksheet, there are some differences you need to be aware of. ### What happens at start up? When you first add an dashboard extension to a dashboard or viz extension to a worksheet, Tableau reads the extension's manifest file (`.trex`) and checks for errors. If the manifest file passes validation, Tableau registers the extension, loads the web page specified in the URL, and then caches information about the extension, such as the URL, the name of the extension, and the version number. Upon loading the web page, the extension runs the initialization code (`tableau.extensions.initializeAsync()`) that initializes the Extension API and then runs any JavaScript promises specified by the extension. ### Making changes to the manifest file If you make changes to the manifest file after the extension is loaded and you want to see the results those changes, you need to remove the extension from the dashboard or worksheet and then add it again. For example, if you were to change permissions for data access, or to add a configuration menu to your dashboard extension, you need to do the following: 1. Remove the extension from the dashboard or worksheet. For dashboard extension, use the **More Options** menu (in the upper-right corner of the dashboard extension container) and select **Remove from Dashboard**. For viz extensions, go to Worksheet > Manage Viz Extensions and use the dialog box to remove the extension. 1. Add the dashboard or viz extension back again. For dashboard extensions, drag the Extension object on to the dashboard and select your extension again. For viz extensions, click the Marks card and click Add Extension. Tableau loads the extension using the settings from the updated manifest file. ### What happens when you reload the dashboard extension in Tableau? You can reload a dashboard extension’s initial web page and state when you select **Reload** from the **More Options** menu in the upper-right corner of the dashboard container. This is equivalent to performing a hard-refresh in a browser. This is useful if you are making changes to the HTML pages or JavaScript code and you want to check your progress and see your changes in action. When you reload the extension this way, it does not pick up any changes you might have made to manifest file. For that, you need to remove the extension and add it back again. Note that if you want to reload the page and you are using Chromium or another browser to debug your web application, you will need to close the browser and restart the session. This is because of a bug with our embedded browser. The **Reload** option actually tears down and re-creates the browser control, which means you’ll need to establish a new debugging session whenever you click **Reload**. ### Making changes to a configuration dialog box If your extension opens a configuration dialog box, or otherwise calls the `tableau.extensions.initializeDialogAsync()` method, you don't need to use the **Reload** option to see updates to your dialog box. You just need to close the dialog box and then either use the **Configure...** menu or click the control in the parent window that reopens the dialog box. --- ## Security and Data Access --- ## Accessing Underlying Data The Extensions API provides several methods that can access the underlying data in a dashboard. The underlying data can include information about the data sources, such as the names of the connection, fields, and tables. Collectively, this information is sometimes called *full data*. Because of the potentially sensitive nature of this data, dashboard authors and people who use extensions need to know if the extension can access their data, and based upon this knowledge they might want to restrict this access. To help ensure transparency and to give the users of extensions control, if your extension uses any of these methods that can access full data, you need to configure your extension to require **full-data** permission. The following section describes how you need to set up the permissions for your extension to run. --- Starting in Tableau 2020.2, Tableau introduced a data model that supports flexible table relationships. To support this new model, the Extensions API version 1.4 provides new APIs that support the data model. Because the data model changes the way the underlying data can be stored, some of the existing APIs will no longer work. For more information about the data model in Tableau, see [The Tableau Data Model](https://help.tableau.com/current/pro/desktop/en-us/datasource_datamodel.htm). ### What happens when extensions access full data? As a developer, you can choose the features and capabilities to implement using the Extensions API. One of these is the ability of the extension to access the underlying data in the dashboard or workbook. The `Worksheet` and `Datasource` interfaces have methods that return (as a promise) data tables containing the underlying data of the workbook or data source. The `Datasource` interface has additional methods, `getLogicalTablesAsync()`, `getActiveTablesAsync()` (Deprecated), and `getConnectionSummariesAsync()` that return the names of the active tables and fields in the data source, and the summary descriptions of the data source connections. There are many cases where you need access to this underlying data so that your extension can perform a useful function. The only requirement for access is that the users of your extension are aware of it. If the extension uses any of the four methods, they are considered privileged, in that they require access to underlying data and data source information. To use any one of them, you need to add a declaration to your extension's manifest file (`.trex`) that states the extension requires **full-data** permission. Tableau uses this declaration to provide a prompt to users at run time that gives them the option of allowing this access or not. --- ##### Extensions API methods that access full data If your extension uses any one of the following methods, without declaring **full-data** permission in the manifest file, the extension will load but the method call will fail. If you have debugging enabled, Tableau will report an error in the JavaScript console and the error is also written to the Tableau log file. | Method | Current Status | |------------------------------------------|-------------------------------------| |`Worksheet.getUnderlyingTablesAsync()` | | |`Worksheet.getUnderlyingTableDataAsync()` | | |`Worksheet.getUnderlyingDataAsync()` | Deprecated starting with the v1.4 library | |`Datasource.getLogicalTables()` | | |`Datasource.getLogicalTableData()` | | |`Datasource.getUnderlyingDataAsync()` | Deprecated starting with the v1.4 library | |`Datasource.getActiveTablesAsync()` | Deprecated starting with the v1.4 library | |`Datasource.getConnectionSummariesAsync()`| | If you use any of these APIs, you need to add a `` element to the manifest file (`.trex`) and specify **full-data** permission. The declaration will allow the extension to pass validation. Depending upon the situation, for example, the first time a user adds the extension, Tableau will prompt the user to allow or deny the extension to run. --- ### Add permissions to access full data to manifest file To access the underlying data along or information about the data source, the extension must declare that it requires full data access in the extension manifest file (`.trex`). The `` element you add looks like the following: ```xml ... ... full data ``` The `` element must be added under `` immediately following the `` element. For a complete description of the manifest, see the [Tableau Dashboard Extensions Manifest File](../dashext/trex_manifest.md) and the [Tableau Viz Extensions Manifest File](../vizext/trex_viz_manifest.md). If full data is not declared in the manifest file, and the extensions calls one of the APIs that accesses any underlying data or data source information, the API call fails. In addition, an error is written to the Tableau log file (`log.txt`). If you are debugging the extension with the Chromium web browser, an error is reported the console pane. The error message would look similar to the following: ```cli PermissionValidation.ts:26 Extension (name = DataSources Sample, ID = com.tableau.extensions.demo.local) is missing required permission: full-data Error: internal-error: permission-denied: Missing required permission to run get-underlying-data(...) ``` --- ### What happens when users add your extension? When users add an extension that requires full-data access, Tableau displays a prompt that provides the name of the extension, the URL of the extension, and brief description about the access that the extension has to the data in the workbook. Users can click **Allow** to load the extension. If a user clicks **Cancel**, the extension is not loaded and the user returns to the **Choose an Extension** dialog box. ![](../assets/Add_Extension_Prompt.png) The name of the extension and its URL come from the values you add to the manifest file for the extension. These are the values you add for `` and the `` element under ``. --- ### What happens when users open a workbook that uses an extension? When users open a dashboard that has an extension that requires full-data access, Tableau displays a prompt that lists the names of the all the extensions in the dashboard. Users can click **Allow** to load the dashboard. ![](../assets/Load_Extensions_Dialog.png) The **Allow an Extension** dialog box provides users information about your extension. This information includes links to your website, as specified by the `website` attribute in manifest file (in the `` element). Users can click on the link under **Created By** to find out more about your extension. The dialog box also indicates whether or not the extension requires full-data access and provides the URL of the extension. If a user clicks **Cancel**, the workbook is opened, but the extension is not loaded. In the layout container where the extension would normally appear, the following message appears: ```cli Extension was denied the required permission to run. Permissions approval can be reset through the context menu. ``` Users can use choose to allow the extension to run by using the clicking **Reset Permissions** from the **More Options** drop-down menu of the layout container. This opens up the **Allow Extensions** dialog box where the user can change permissions for the extension. ![](../assets/reset_perms.png) --- ## Add OAuth to Dashboard Extensions If you want your dashboard extension to use OAuth for authentication, you need to be aware of some important changes that were introduced along with sandboxed extensions in Tableau 2019.4. All dashboard extensions are now wrapped in an `iframe`. Because most OAuth sign-in pages enforce the `X-Frame-Options` settings, which helps prevent [clickjacking](https://tools.ietf.org/html/draft-ietf-oauth-v2-23#section-10.13) attacks, you can't load an OAuth sign-in page within the dashboard, or open a dashboard extension dialog window to use for OAuth sign in. As a best practice, dashboard extensions should open an external browser window when you need to request end-user authorization. In Tableau Server and Tableau Cloud, you can do this by using methods like `window.open()` and `window.postMessage()`. However, the `window` methods alone will not work in Tableau Desktop, as the external browser window you open for OAuth sign in has no direct connection to the instance of the dashboard extension. This adds a layer of security and requires extra steps to properly route the access token back to your dashboard extension. The following section describes one possible way of coordinating the OAuth interaction using web sockets for communication and the authorization code grant. For security, the client secret should be stored on the server and not in the client code. This is the only safe way to ensure that someone can't use the client credentials to impersonate your application. To simplify your code, your extension should use the same OAuth flow for Tableau Server and Tableau Desktop. --- ### Summary of the OAuth flow The following outlines some of the basic steps in using a secondary window for OAuth sign in. For more guidance on setting up OAuth 2.0 securely, see [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics). The following summarizes the steps you'd take if you were using the authorization code grant type. This OAuth flow is recommended as it ensures that the access token is not returned in the URL and that the client secret is safely stored on the server. * Open a channel for communication between the server hosting the extension (the server) and the instance of the extension (the client). Create a unique identifier (or session id) for the extension when the extension loads. * Create a request (URL) that directs the user to the OAuth provider sign-in page. The request URL includes the client ID and the extension's unique identifier (session id), and provides a callback URL. Open a new dialog window using `window.open()` directed to the URL. Prompt the user to sign in to the OAuth provider (the provider). * The user signs in to the OAuth provider and allows (or denies) the extension access to data. * When authorization has been granted, the OAuth authorization code is returned to the callback URL (on the server). The server processes the response and extracts the authorization code and the session id. The server creates a new request for the OAuth access token, using the authorization code grant type, and maps the client ID, client secret, and authorization code in the body of the request. * When the response from the OAuth provider is received, the server signals an event that returns the OAuth access token, through the web socket, back to the calling extension. * In response to the signal, the extension retrieves and stores the OAuth token in local storage on the client's computer. The extension can then use the access token to send subsequent requests for services from the OAuth provider. --- ```mermaid sequenceDiagram participant User participant E as Extension (client) participant S as Server (web host) participant O as OAuth provider User->>E: Load dashboard extension E->>S: Open communication channel S-->>E: Use this socket session ID (socket.id) E->>O: Request the OAuth login page (with client ID) O-->>User: Prompt user: Allow or deny access? User->>O: Allow! O-->>S: Here's the auth code S->>O: Auth code request + client ID, client secret, session ID O-->>S: Here's the access token Note right of E: Route the access token based on session ID S->>E: This access token is for this session ID. Note right of E: Use the access token to request data ``` --- ### An example of the OAuth code path using Socket.IO One way to manage OAuth sign in for your dashboard extension is to employee web sockets for identifying and coordinating the OAuth connection. For example, the OAuth sample ([datadev-oauth-sign-in](https://glitch.com/~datadev-oauth-sign-in) on Glitch makes use of the [Socket.IO](https://socket.io) library. Socket.IO provides bi-directional event-based communication between the browser (client) and server. This example uses Node.js and Express to setup a web server to host an extension that connects to Spotify. The extension uses sockets to identify the extension and to route communication with the OAuth sign-in window. #### Initialize the socket (session ID) In the OAuth sample, when the extension loads in the dashboard, a unique socket session ID is generated. The socket instance is assigned a random 20-character identifier (`socket.id`). This code is on the client-side, the extension's web page (`index.js`). The code also initializes the sign-in button on the extension's home page. ```javascript const socket = io(); // Wait to make sure you've made the socket connection so when you press the button there is a socket ID available to send with the request socket.on("connect", () => { $("#submit").prop("disabled", false); }); ``` On the Express server (`server.js`), in addition to serving the web pages, the server initializes `socket.io`. ```javascript const express = require("express"); const app = express(); const server = require("http").createServer(app); const io = require("socket.io")(server); const fetch = require("node-fetch"); ``` #### Sign in and get the authorization code When a user clicks the sign-in button, it opens a new window or tab in the user's native browser (`window.open`) passing with it the client ID and the socket session ID (`socket.id`). In this example, the URL goes to the authorization endpoint on Spotify and specifies the authorization code response type. Note that this sample uses the client ID for the OAuth sample project on Glitch. You need to change this to match your client ID for your dashboard extension. The method also provides a redirect URL back to the server, where the response will return the authorization code. ```javascript // Open a new window to manage the OAuth process outside of Desktop, passing the socket ID so the broker knows where to return the token function openSignInWindow() { const scopes = "user-top-read"; const redirect_uri = "https://datadev-oauth-sign-in.glitch.me/complete"; const url = "https://accounts.spotify.com/authorize?response_type=code&client_id=" + "2029812009c54aef866bd660f612b603" + "&scope=" + encodeURIComponent(scopes) + "&redirect_uri=" + encodeURIComponent(redirect_uri) + "&state=" + socket.id; window.open(url, "_blank"); } ``` #### Request the access token (server-side) The redirect URL sends the response to the server, where the server extracts the authorization code. The server then sends a new request for the OAuth access token, providing the client ID, the client secret, and authorization code. Making this request from the server ensures that the client secret is kept protected and out of the client-side browser code. In this example, the request is made as part of an `async` function call and the sample makes use of the node-fetch module. There is a lot going on here. After sending the request, the server waits for the response from Spotify that returns the OAuth access token. When the access token is received, the server emits a `signedin` event that signals the instance of extension that initiated the sign in. The server redirects the client browser to the complete page and sends the client the access token, so that the extension can make the authorized requests from Spotify. ```javascript app.get("/complete", async (req, res) => { const code = req.query.code; const sessionid = req.query.state; const url = "https://accounts.spotify.com/api/token"; const parameters = { grant_type: "authorization_code", client_id: process.env.SPOTIFY_CLIENT_ID, client_secret: process.env.SPOTIFY_CLIENT_SECRET, code, redirect_uri: "https://datadev-oauth-sign-in.glitch.me/complete" }; const body = Object.keys(parameters) .map(key => `${key}=${encodeURIComponent(parameters[key])}`) .join("&"); const options = { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body }; const response = await fetch(url, options); const data = await response.json(); io.to(sessionid).emit("signedin", data.access_token); res.sendFile(__dirname + "/views/complete.html"); }); ``` #### Pass the access token to the client and make requests In response to the `signedin` event, the client checks to see if the OAuth access token was received. If the token is present, the client stores it locally and then uses the access token to make a new request to retrieve the user's top artist from Spotify. In case of error, the access token is removed from local storage and the user is asked to sign in again. ```javascript socket.on("signedin", function(token) { if (token) { localStorage.setItem("spotifyAuth", token); getTopArtist(token); } else { console.error("Token response was empty!"); } }); // Get the top artist information with the token, if the request fails, prompt again for auth async function getTopArtist(token) { const url = "https://api.spotify.com/v1/me/top/artists"; const options = { headers: { Authorization: "Bearer " + token } }; try { const response = await fetch(url, options); const data = await response.json(); updateTopArtist(data); } catch (error) { localStorage.removeItem("spotifyAuth"); openSignInWindow(); console.error(error); } } // Updated the text in the dashboard extension function updateTopArtist(data) { $("#signIn").hide(); $("#name").html("" + data.items[0].name + " is your favorite artist!"); } ``` #### Next steps * Use the sample code ([datadev-oauth-sign-in](https://glitch.com/~datadev-oauth-sign-in)) on Glitch as a starting point to build your own OAuth solution. * Review the latest [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics) draft documentation. --- ## Create and Test Sandboxed Extensions To provide security for customers, Tableau supports a type of dashboard or viz extension that runs in a sandbox. These Sandboxed Extensions are hosted by Tableau and use W3C standards, such as Content Security Policy (CSP), to ensure the extension can't make network calls outside the hosting Tableau Server. This means a Sandboxed Extension can query data in the workbook using the Extensions API, but it can't send that data anywhere outside the sandbox. The results of the query are on the client computer (in the browser) and can't be sent anywhere. This topic provides information to help you get started creating and testing Sandboxed Extensions. :::note If your extension requires resources of outside services, you shouldn't create a Sandboxed Extension. Dashboard and viz extensions that don't run in the sandbox environment are called Network Enabled Extensions. While Sandboxed Extensions are allowed to run by default on Tableau Server and Tableau Cloud, Network Enabled Extensions require server and site administrator approval and must be added to safe list for a site. ::: --- ### Sandboxed Extensions and Network Enabled Extensions Tableau supports two extension types: * **Network Enabled Extensions** - can access resources and applications outside of Tableau. Supported in Tableau 2018.2 and later. * **Sandboxed Extensions** - run in a Tableau hosted environment and cannot make network calls. Supported in Tableau 2019.4 and later. ### Create Sandboxed Extensions You can create Sandboxed Extensions from scratch, or you can port your existing (Network Enabled) extensions to work in the sandbox environment. The components of a Sandboxed Extension are the same as for a Network Enabled one. Each extension consists of a web page that calls your JavaScript code and the Extensions API JavaScript library. Each extension provides a `.trex` file. To port an existing extension, make sure all resources and libraries are local to your extension source code. No network access outside the sandbox container is allowed. And you need to modify the path to your extension in the `.trex` file to have it load in the sandbox environment. Information about changing your `.trex` file is described in [Test your own Sandboxed Extensions in the local sandbox](#test-your-own-sandboxed-extensions-in-the-local-sandbox). #### Requirements for Sandboxed Extensions * Sandboxed Extensions cannot make calls to remote or external resources or libraries. All content must be local to the extension. For example, if your extension uses the JQuery library, the library can't be accessed via HTTPS (`src="https://code.jquery.com/jquery-3.2.1.min.js"`). The library must be downloaded and on the same computer as the extension. All links must be relative (`src="./jquery-3.2.1.min.js`). * All resources must be static files (`.js`, `.css`, images, etc.). * Resources should be placed in the same folder as your extension web page(s), or in a sub-folder of that directory. All references must be relative. * Review the suggested guidelines that all extensions should follow to reduce XSS vulnerabilities, see [Cross-Site Scripting and Extension Security](./trex_xss_guidance.md). * Sandboxed Extensions cannot connect to sites outside of the sandbox and cannot transfer data. For example, a Sandboxed extension cannot transfer data to be processed by other applications. If you need to connect to outside services or resources, you can create a Network Enabled extension. Network Enabled extensions can't run in the local sandbox environment or in the Tableau Hosting Cloud Service for Sandboxed Extensions. * **React.js** - If you are creating your Sandboxed Extension using React.js, routing will not work, including hash-based routing (`.../index.html#/configDialog`) in your URL. If you want to use multiple pages in your extension, the alternatives to routing in React.js include using [react-app-rewired](https://www.npmjs.com/package/react-app-rewired) and [react-app-rewire-multiple-entry](https://www.npmjs.com/package/react-app-rewire-multiple-entry), or [Gatsby](https://www.gatsbyjs.org/). * Sandboxed Extensions must be tested in the local sandbox environment. Issues discovered with the extension must be fixed and verified before submitting the Sandboxed Extension package to Tableau. --- ### Sandboxed Extensions development environment The Extensions API SDK provides a local development environment that replicates the Tableau Hosting Cloud Service for Sandboxed Extensions. You can test your Sandboxed extensions locally with the same sandbox policies before submitting the extension to Tableau for publication. See [Publish Sandboxed Extensions](../publish/trex_sandbox_publish.md) for information about making your extension available to others in the cloud service for Sandboxed Extensions. The local development environment can't be used to deploy or publish your extension. --- ### Install and start the local sandbox environment The following instructions assume that you have downloaded or cloned the Extensions API GitHub project. 1. Go to the `extensions-api` folder. 2. If you haven't done so already, install the web server the sandbox test components: **npm install** 3. Start the sandbox server: **npm run start-sandbox** --- ### Test the sample Sandboxed Extension The Extensions API GitHub project provides an example of a Sandboxed Extension. The Sandboxed Extension is configured to run in the test sandbox environment. 1. After you start the local sandbox environment, start Tableau Desktop or Tableau Server 2019.3 (or later) and open a workbook that has a dashboard, or open a workbook and create a new dashboard. 2. In the dashboard, under **Objects**, select **Extension** and drag it on to the dashboard. 3. In the **Choose an Extension** dialog box, click **My Extensions**, and then navigate to the `Extensions API\Samples\Dashboard\UINamespace-sandboxed` folder. Open the `uiNamespace.trex` file. The sample extension (web application) appears in the dashboard frame. The UINamespace-sandboxed sample finds and displays the data source for each worksheet in the dashboard and lets you configure the refresh rate. The **UINamespace-sandboxed** sample is a *sandboxed* version of the Network Enabled Extension, UINamespace sample. You can compare the source files for both extensions to better understand the differences and to see what it takes to port existing extensions (Network Enabled) to be Sandboxed Extensions. The sample extension makes use of a popup dialog box for configuration. --- ### Test your own Sandboxed Extensions in the local sandbox When you start the local sandbox server (**npm run start-sandbox**), the server reads from the `sandbox-config.json` file. This sandbox configuration file defines the port the server uses (the default is set to `8765`) and provides the names and locations of the Sandboxed Extension web pages. You can modify the file to change the port or to add your own Sandboxed Extensions for testing. Before testing your extension in the sandbox, check to make sure that you are only loading resources and libraries from the folder that contains your extension's web page. See [Requirements](#requirements-for-sandboxed-extensions). #### Add your extension to the sandbox-config.json file If you have an extension that is ready to test in the sandbox, follow these instructions. 1. Open the `sandbox-config.json` file. The configuration file has two main entries. The first, specifies the `port` that the sandbox web server will use. You can change this as needed, however, it must match the port you specify in the `.trex` file for your extension. The `sandbox-config.json` file has one entry for the sandboxed version of the UINamespace sample. The file looks like the following: ```json { "port": 8765, "extensions": { "uinamespace": { "path": "./Samples/Dashboard/UINamespace-sandboxed" } } } ``` 2. Add an new entry under `"extensions"`, for your extension. The syntax for an entry is as follows. ```json "name": { "path": "relative-or-absolute-path-to-extensions-folder-on-disk" } ``` Replace `name` with the name of your extension. This name is the ID for the extension and becomes part of the URL. The local server for Sandboxed Extensions hosts pages with the following syntax. ```html http://localhost[:port]/sandbox/[name] ``` For example, the default value for port is `8765`. If the name of the extension is `uninamespace`, that name becomes part of the URL that you specify in the `.trex` file: `http://localhost:8765/sandbox/uinamespace/`. Note that in the `.trex` file the full URL also includes the name of the web page in the URL. An implied `index.html` does not work. For the `"path"` value, provide the relative or absolute path to the folder on your computer that contains your extension web page and source files. The path is relative to the `extensions-api` parent folder. For example, the `uinamespace` sample is in the `.\Samples\Dashboard\UINamespace-sandboxed` folder. The following example adds an extension called `helloworld` that has its source files in the `./mySamples/HelloWorld` folder. You can enter multiple extensions. Be sure to separate each entry with a comma (,). ```json { "port": 8765, "extensions": { "uinamespace": { "path": "./Samples/Dashboard/UINamespace-sandboxed" }, "helloworld": { "path": "./mySamples/HelloWorld" } } } ``` 3. Restart the local server (**npm run start-sandbox**). The local Sandboxed Extensions server only reads the configuration file at start up. Anytime you make changes to the `sandbox-config.json` file you need to stop and restart the server. #### Use the local sandbox path to the extension in the .trex file In the `.trex` file for your extension you need to specify the URL of extension as it will appear on the local sandbox web server. The URL is constructed from several parts. The `` for `` must use the name of local server (`localhost`) and port setting assigned in the `sandboxed-config.json` file (`8765` is the default). This is followed by `sandbox` and the name of your extension, as you specified in the `sandbox-config.json` file. Finally, you specify the name of the HTML page that serves as the home page for your extension. The following example shows the URL for the UINamespace-sandboxed extension. ```xml http://localhost:8765/sandbox/uinamespace/uinamespace.html ``` After you update the `.trex` file to point to the sandboxed instance, you can start the local sandbox sever (**npm run start-sandbox**), and then open Tableau and try it out. For information about making your Sandboxed Extension available to others, see [Publish Sandboxed Extensions](../publish/trex_sandbox_publish.md). The local development environment can't be used to deploy or publish your extension. ### Troubleshoot the test environment To troubleshoot issues with your Sandboxed Extension in the test environment, you should start a debugger so you can observe if there are any problems with your setup, such as missing files and resources, and to see if there are any instances where your extension generates any CSP errors and warnings. For information about using debugging tools, see [Debug Extensions in Tableau Desktop](../debug/trex_debugging.md). Error `Cannot find extension with id: ` If you see this error in the console of your debugging tools, be sure the name (or ID) of the extension in the `sandbox-config.json` file matches the name you specified in the `` in the `.trex` file. --- ## HTTPS and Tableau Extensions A Tableau extension is essentially a web application that runs inside a Tableau worksheet or dashboard. The extension can interact with other components in the dashboard and potentially has access to the visible and underlying data in the workbook (through a well-defined API). In addition, the web application can be running code on a server that sits outside of the domain where Tableau Server or Tableau Desktop are located. For security, Tableau requires the following: * All extensions must use the HTTP Secure (HTTPS) protocol. * To run on Tableau Server, the URL of the extension must be added to a safe list. The Tableau Server site administrator manages this list. * By default, anyone using the extension will be prompted and asked to allow or deny the extension access. The Tableau Server site administrator can control whether the prompt appears for each extension. This section covers options for setting up your extension to use HTTPS. For information about adding an extension to the safe list on Tableau Server or Tableau Cloud, or how to configure the prompts to allow or deny access, see [Manage Dashboard Extensions on Tableau Server](https://onlinehelp.tableau.com/current/server/en-us/dashboard_extensions_server.htm) or [Manage Dashboard Extensions on Tableau Cloud](https://onlinehelp.tableau.com/current/online/en-us/dashboard_extensions_server.htm) for more information. --- ### Why HTTPS? When you use HTTPS, all HTTP data is encrypted prior to transmission by the Transport Layer Security (TLS) and decrypted when it is received. HTTPS ensures a secure channel between the extension (client), running in the Tableau dashboard, and the web server that hosts the extension. The use of HTTPS provides a level of privacy, authentication, and integrity. If you plan to make your extension available to others, using HTTPS assures your customers that their data is safe and that they are connecting to a trusted extension. Because the extension is using HTTPS, Tableau is also able to verify the identity of the server that hosts the extension, which prevents various malicious man-in-the-middle attacks that could occur if the extension were to use HTTP alone. > Note: For development or internal use, you can run your extension on your local computer as `localhost` and you can use the HTTP protocol. See [Use HTTP and localhost for development or internal use](#use-http-and-localhost-for-development-or-internal-use). --- ### HTTPS and security requirements The requirements are pretty straight-forward. If you are distributing your extension so that others can use it, the extension must be hosted on a web server that is configured to support the HTTPS protocol. * The server that hosts your extension must have a certificate from a Certificate Authority (CA). There are many free and low cost options. Note that self-signed or test-signed certificates are not sufficient. The certificate is sometimes called an SSL certificate, as HTTPS was formerly implemented by the Secure Sockets Layer (SSL). * In the `.trex` file for your extension, the `url` you use for the source location must start with `https://`. If the HTTPS protocol is not specified, the extension fails registration and will not load in Tableau. The exception to this requirement is for `localhost`. If you are developing your extension, you can host it on your computer using HTTP (for example, `http://localhost`). You can also use `http://localhost` if you publish the workbook to Tableau Server. In this case, the extension must be running on the same computer as the browser you are using to access the server. * Mixed content is not allowed. If your web application uses other libraries or resources, those assets should also use `https`, or use site-relative links. * Redirects are permitted, but if they redirect to any other origin, other than the URL of the extension, those pages cannot interact with the Extensions API. For example, if the URL of your extension is `https://example.com` and you redirect to `https://myexample.com`, the page you were redirecting to (`https://myexample.com`) cannot interact with the Extensions API. * To run on Tableau Server or Tableau Cloud, your extension must be added to the safe list for the site. Server administrators or site administrators (Tableau Cloud) can add or remove extensions, and can configure how an extension requests permissions for access to data. --- ### Setting the source location in the .trex file The source location of the extension is its URL. You specify the URL in the extension manifest (`.trex`) file. The syntax includes the name of the server, which includes the protocol (HTTPS, HTTP), the port used, if any, and the path to the extension web page (optional). The `` element in the `.trex` file, looks like the following: ```xml SCHEME://SERVER[:PORT][/PATH] ``` For example, if the extension is hosted on example.com, the entry might look like the following: ```xml https://example.com:4043/extension ``` Or during development, when you can use `localhost`, the entry might look like the following: ```xml http://localhost:8080/extension ``` At startup, Tableau looks for and validates the extension `.trex` files. If the extension source location does not specify HTTPS or localhost, validation fails and the extension is not registered. These errors are written to the log file. The extension will not load in Tableau. If you examine the log files (for example, with Tableau Log Viewer), you will find something similar to the following: ```cli Error: Registration Failed: XSD Validation Failed file: Example.trex Error: Error(11,67): value 'http://example.com/example.html' does not match regular expression facet '[Hh][Tt][Tt][Pp][Ss]://.+|[Hh][Tt][Tt][Pp]://[Ll][Oo][Cc][Aa][Ll][Hh][Oo][Ss][Tt]|[Hh][Tt][Tt][Pp]://[Ll][Oo][Cc][Aa][Ll][Hh][Oo][Ss][Tt][:/].*' (id: C:\Users\\Documents\My Tableau Repository (Beta)\Extensions\example.trex) file: Example.trex ``` For more information, see [Use Log files to Troubleshoot Dashboard Extensions](../debug/trex_logging.md). ### Avoid mixed content - use HTTPS or site-relative paths for resources The extension should not call any pages over HTTP. If you reference libraries or resources, specify `https` for the files you include. For example: ```html ``` Or use site-relative or protocol-agnostic links (that is, where the protocol is not specified). ```html ``` ### Use HTTP and localhost for development or internal use While HTTPS is required, during development, you can run a web server on your local computer (`localhost`) to host the extension over HTTP. This exception also applies to Tableau Server and Tableau Cloud. For example, you can author a workbook in Tableau Desktop that uses an extension (running as `localhost` on same computer as Tableau Desktop). You can then publish that workbook to Tableau Server or Tableau Cloud. The extension must be running on the same computer as the browser that you are using to connect to Tableau Server. The `localhost` is local to the browser. For example, the source location of an extension might be `http://localhost:8080/extension`. If you publish that extension to Tableau Server, other users can view the dashboard and extension, provided that they also have a local copy of the extension hosted on their computers and the extension is using the same URL. Note the following considerations: - If Tableau Server is using HTTPS, your extension might not load if it is using `http://localhost`. The same situation occurs with Tableau Cloud. This is because it is generally not a good practice to embed an HTTP `