Bubble connects to Tableau via the API Connector using a two-step Personal Access Token (PAT) authentication flow. You first exchange your PAT for a short-lived session token, store it in App State, and then pass it as a dynamic header on every subsequent workbook query or refresh call. An optional iframe embed lets you display Tableau Public views inside Bubble pages without any API calls.
| Fact | Value |
|---|---|
| Tool | Tableau |
| Category | Analytics |
| Method | Bubble API Connector |
| Difficulty | Intermediate |
| Time required | 2–3 hours |
| Last updated | July 2026 |
Why connecting Bubble to Tableau requires a two-step auth flow
Most API integrations use a static API key: paste the key once as a Private header and every call just works. Tableau is different. Its REST API uses a Personal Access Token (PAT) only as a one-time credential to obtain a short-lived session token — similar to how OAuth exchanges a code for an access token. The session token expires in 4 hours, which means Bubble apps left open overnight will silently fail on the next API call unless you add a re-authentication workflow.
The payoff for this extra complexity is a powerful use case: a Bubble admin panel that gives your operations team a live view of Tableau workbooks and the ability to trigger on-demand data refreshes without needing a Tableau login. Combined with an optional iframe embed for display-only stakeholder views, Tableau's analytics power becomes accessible inside the browser-based tool your team already uses. Bubble's API Connector is perfectly suited here because all calls run server-side — your PAT and session token never touch the browser.
Integration method
Connects to the Tableau REST API using a Personal Access Token exchanged for a short-lived session token, with an optional iframe embed for display-only views.
Prerequisites
- A Tableau Cloud account (or Tableau Server with REST API access enabled) on a plan that includes REST API access
- A Tableau Personal Access Token (PAT) — created in your Tableau Cloud account settings under My Account → Personal Access Tokens
- Your Tableau Cloud pod URL (e.g., https://10az.online.tableau.com) and site name (e.g., 'mycompany') — both visible in the address bar when logged in
- A Bubble app on any plan for read-only workbook listing and embed; a paid Bubble plan if you want to use API Workflows to schedule re-authentication
- The API Connector plugin installed in your Bubble app (free, by Bubble)
Step-by-step guide
Create a Tableau Personal Access Token
In Tableau Cloud or Tableau Server, click your avatar in the top-right corner and select My Account Settings. Scroll to the Personal Access Tokens section and click Create new token. Give the token a meaningful name such as 'Bubble Integration' and copy both the token name and the token secret that appear — the secret is shown only once. Store them temporarily in a text file. You will use the token name and secret as the body of your sign-in API call, not as a header. Do not confuse the PAT secret with the session token you will receive in response — the PAT is the long-lived credential you keep safe; the session token is ephemeral and will be stored in Bubble App State.
Pro tip: If your Tableau Server administrator has disabled PATs, you can use username/password authentication in the sign-in body instead — replace 'personalAccessTokenName' and 'personalAccessTokenSecret' with 'name' and 'password' fields. PATs are preferred because they can be revoked individually without changing your account password.
Expected result: You have a PAT name string (e.g., 'BubbleIntegration') and a PAT secret string (a long alphanumeric value). Both are stored safely and will be used in the next step.
Add the Tableau API Connector group with a shared Accept header
In your Bubble app, open the Plugins tab in the left sidebar and click Add plugins. Search for 'API Connector' (by Bubble), install it, then close the plugin dialog. Open the API Connector plugin settings. Click Add another API. Name it 'Tableau'. In the Shared headers for this API section, add one header: key is 'Accept', value is 'application/json'. Do NOT mark this header Private — it is not a secret. This Accept header is critical because Tableau's REST API defaults to returning XML. Without it, every response will be XML and Bubble will be unable to parse the fields automatically during initialization. Leave the Authentication field set to None — authentication is handled by the X-Tableau-Auth header added dynamically on each call after sign-in.
1{2 "api_group": "Tableau",3 "shared_headers": {4 "Accept": "application/json"5 },6 "authentication": "None"7}Pro tip: Also add 'Content-Type: application/json' as a second shared header to ensure POST bodies are parsed correctly on the Tableau side.
Expected result: The 'Tableau' API group appears in the API Connector plugin with a shared Accept: application/json header and no authentication scheme.
Add the Sign In call and store the session token in App State
Inside the Tableau API group, click Add another call. Name it 'Sign In'. Set Method to POST and the URL to 'https://{your-pod}.online.tableau.com/api/3.19/auth/signin' — replace {your-pod} with your actual Tableau Cloud pod identifier (e.g., '10az'). Under Use as, select Action (not Data) — this call will be triggered from a Bubble workflow, not bound to a data source. In the Body section, select JSON and paste the sign-in payload shown below. Replace the placeholder values with your actual PAT name and secret for the purpose of the Initialize call — you can make them dynamic later. Click Initialize call. Bubble will make a real POST to Tableau and parse the JSON response. The response contains 'credentials.token' (the session token) and 'credentials.site.id' (the site UUID). After initialization, go to your Bubble app's Data tab and create two App State variables: 'tableau_session_token' (text) and 'tableau_site_id' (text). Then build a workflow — for example triggered on Page is Loaded — that runs the Sign In action and saves the returned 'credentials token' value to 'tableau_session_token' state and 'credentials site id' to 'tableau_site_id' state. Note: the site ID UUID from the sign-in response differs from the site name you see in the URL — always use the UUID in subsequent API paths.
1{2 "method": "POST",3 "url": "https://10az.online.tableau.com/api/3.19/auth/signin",4 "headers": {5 "Content-Type": "application/json",6 "Accept": "application/json"7 },8 "body": {9 "credentials": {10 "personalAccessTokenName": "BubbleIntegration",11 "personalAccessTokenSecret": "<your-pat-secret>",12 "site": {13 "contentUrl": "mycompany"14 }15 }16 }17}Pro tip: The 'contentUrl' in the body is your Tableau site name as it appears in the URL after '/site/' — for the Default site on Tableau Server it is an empty string "". Get it exactly right or you will receive a 404 on the sign-in call.
Expected result: The Initialize call succeeds and Bubble detects fields including 'credentials token' (the session token string) and 'credentials site id' (a UUID). A page-load workflow saves both to App State, making them available to all subsequent calls.
Add the List Workbooks call with a dynamic session token header
Back in the Tableau API group, click Add another call. Name it 'List Workbooks'. Set Method to GET and URL to 'https://{your-pod}.online.tableau.com/api/3.19/sites/<site_id>/workbooks' — note the '<site_id>' placeholder. Set Use as to Data. In the Parameters section, add a parameter named 'site_id' — mark it as a path parameter, not a query parameter. This will be supplied dynamically from App State when the call is made. Now add a header at the call level (not the group level): key is 'X-Tableau-Auth', value is your hardcoded test session token from the sign-in step above. Mark this header Private (click the checkbox). This allows you to initialize the call with a real session token. After initialization succeeds, edit the X-Tableau-Auth header value to be a dynamic value from App State ('tableau_session_token'). Bubble's API Connector supports dynamic headers on calls by switching from static text to a dynamic data reference. The response will nest workbooks under 'workbooks workbook' — Bubble's field detector will map this as a list. Set 'Type of content' to List to enable use in a Repeating Group.
1{2 "method": "GET",3 "url": "https://10az.online.tableau.com/api/3.19/sites/<site_id>/workbooks",4 "headers": {5 "Accept": "application/json",6 "X-Tableau-Auth": "<private: dynamic from App State>"7 },8 "parameters": {9 "site_id": "<dynamic: tableau_site_id from App State>"10 }11}Pro tip: During initialization, Bubble may warn that the detected response shape changes if you re-initialize with a different session token. This is normal — the workbook list structure is the same; only the data values change. Click 'Save' after initialization even if the list appears empty; Bubble saves the field structure.
Expected result: The List Workbooks call initializes successfully. Bubble detects workbook fields including name, id, contentUrl, and updatedAt. A Repeating Group on your page bound to this call displays your Tableau workbooks by name.
Add the Refresh Workbook call and wire it to a button
Add a third call in the Tableau API group. Name it 'Refresh Workbook'. Set Method to POST and URL to 'https://{your-pod}.online.tableau.com/api/3.19/sites/<site_id>/workbooks/<workbook_id>/refresh'. Add path parameters 'site_id' and 'workbook_id' — both will be supplied dynamically. Add the X-Tableau-Auth header at the call level, marked Private, set dynamically from App State. Set Use as to Action. Leave the body empty — Tableau's refresh endpoint does not require a request body. Click Initialize call using a real site_id and a real workbook_id from your List Workbooks response. The response returns a job object with a job ID — Bubble's workflow will see it as completed. Important: the Tableau workbook refresh is asynchronous. The POST does not wait for the refresh to finish; it returns immediately with a job ID. You cannot poll for completion inside a single Bubble workflow due to Bubble's 30-second workflow timeout. Design your UI to show 'Refresh requested' after the button click rather than waiting for completion. In your Bubble editor, add a button in the Repeating Group of workbooks. In the button's workflow, add step 1: 'Plugins → Tableau - Refresh Workbook', supplying the current row's workbook id and tableau_site_id from App State. Add step 2: 'Element Actions → Set state' to show a 'Refresh requested' text element for that row.
1{2 "method": "POST",3 "url": "https://10az.online.tableau.com/api/3.19/sites/<site_id>/workbooks/<workbook_id>/refresh",4 "headers": {5 "Accept": "application/json",6 "X-Tableau-Auth": "<private: dynamic from App State>"7 },8 "parameters": {9 "site_id": "<dynamic: tableau_site_id from App State>",10 "workbook_id": "<dynamic: current cell's workbook id>"11 },12 "body": {}13}Pro tip: Workload units (WU) in Bubble are consumed by every API call. If your admin team is likely to click 'Refresh' repeatedly, consider adding a confirmation popup before the workflow runs to prevent accidental WU consumption from rapid repeated refreshes.
Expected result: Clicking the Refresh button in the workbook Repeating Group triggers the refresh call, returns a 200 response with a job ID, and the UI updates to show 'Refresh requested' for that row. The actual workbook data refresh completes in Tableau in the background.
Optionally embed a Tableau view in a Bubble HTML element
For display-only use cases — particularly sharing a Tableau Public view or an internally shared Tableau Cloud view with non-technical stakeholders in your Bubble app — you can embed the view without any API calls. In Tableau, open the view you want to share and click Share (top toolbar) → Embed Code. Copy the iframe src URL, which will look like 'https://public.tableau.com/views/{workbook-name}/{view-name}'. In your Bubble app, add an HTML element to the page (from the Visual elements panel in the editor). Set the element's height to a fixed value like 600 or 700 pixels — Bubble's responsive engine does not auto-size iframes. Paste the following HTML, replacing the src with your embed URL. The '?:embed=y&:showVizHome=no' parameters are required to suppress Tableau's navigation bar inside the iframe. Set the element's width to 100% of its container using Bubble's layout settings. Note: private Tableau Cloud reports (not Tableau Public) will show a Google sign-in prompt inside the iframe unless the viewer is already authenticated with the correct Google account in that browser session. This is a Tableau security feature, not a Bubble setting — the only solution is to share the report publicly or use Tableau's Trusted Authentication (advanced, server-level configuration).
1<iframe2 src="https://public.tableau.com/views/YourWorkbookName/YourViewName?:embed=y&:showVizHome=no"3 width="100%"4 height="600"5 frameborder="0"6 allowfullscreen>7</iframe>Pro tip: RapidDev's team has built hundreds of Bubble apps with integrations like this — including Tableau admin panels with custom re-auth logic. Book a free scoping call at rapidevelopers.com/contact if you want expert help wiring the token refresh or embedding private views.
Expected result: A live Tableau view renders inside your Bubble page within an HTML element. The visualization responds to Tableau's built-in interactivity (filter dropdowns, tooltip hovers) without any additional Bubble workflow setup.
Common use cases
On-demand workbook refresh panel
Build a Bubble admin page that lists all Tableau workbooks from a specific site and provides a 'Refresh now' button for each. Operations staff can trigger a data refresh directly from Bubble without navigating to Tableau, while the refresh job status ('requested') appears inline. This is especially useful when Tableau workbooks pull from a database that Bubble also writes to — staff can refresh immediately after a bulk data import.
Copy this prompt to try it in Bubble
Executive dashboard with embedded Tableau view
Embed a Tableau Public or internally shared Tableau Cloud view inside a Bubble page using an HTML element iframe. Executives see live BI visualizations alongside editable Repeating Groups of the same underlying data — a combined BI and operations layer in a single Bubble screen, with no separate Tableau login required for the display portion.
Copy this prompt to try it in Bubble
Workbook directory for non-technical stakeholders
Pull the list of all published workbooks on a Tableau site via the API and display them in a searchable, filterable Bubble Repeating Group. Each card shows the workbook name, last-updated timestamp, and a direct link to the Tableau Cloud view. Non-technical stakeholders access workbooks through a familiar Bubble interface instead of navigating Tableau's own project browser.
Copy this prompt to try it in Bubble
Troubleshooting
Sign In call returns 401 Unauthorized during API Connector initialization
Cause: The PAT name or secret is incorrect, the PAT has been revoked, or the 'contentUrl' site name in the request body does not match your Tableau site identifier exactly.
Solution: Double-check the PAT name (case-sensitive) and secret in your Tableau account settings under My Account → Personal Access Tokens. Confirm the contentUrl matches the site name shown in your Tableau Cloud URL after '/site/' — the Default site uses an empty string "". Regenerate the PAT if necessary.
List Workbooks call returns 404 after sign-in succeeds
Cause: The site_id in the workbooks endpoint URL is using the site name (URL string) rather than the UUID returned by the sign-in response. Tableau requires the UUID in REST API paths.
Solution: In your page-load workflow, save the 'credentials site id' value (the UUID, e.g., 'a1b2c3d4-...') to App State, not the site name. The UUID appears in the sign-in response under 'credentials → site → id'. Verify the saved value in Bubble's debugger by inspecting App State after the sign-in workflow runs.
All Tableau API calls start returning 401 after the app has been open for several hours
Cause: Tableau session tokens expire after 4 hours. Once expired, every subsequent call using the stored token fails silently or returns 401.
Solution: Add a page-load workflow that re-runs the Sign In call and refreshes the App State token every time a user loads any page. For apps used over long sessions, add a scheduled client-side event (using Bubble's built-in recurring event or a JavaScript setInterval) that calls the sign-in workflow every 3.5 hours to pre-empt expiry.
API Connector returns 'There was an issue setting up your call' during the List Workbooks initialization
Cause: The X-Tableau-Auth header is missing or contains an expired/invalid session token during the Initialize call step. Bubble needs a real, successful API response to detect field shapes.
Solution: During setup, temporarily hardcode a fresh session token (from a just-completed sign-in call) directly in the X-Tableau-Auth header value field — do not use a dynamic reference yet. Complete the initialization, then switch the header value to dynamic (App State reference) once Bubble has saved the field structure.
Tableau embed iframe shows a blank white box or a sign-in prompt inside the Bubble HTML element
Cause: Either the Tableau Cloud report sharing is set to 'Restricted' (not 'Anyone with the link'), or the viewer's browser is blocking cross-origin iframes via Content Security Policy.
Solution: In Tableau, open the report and go to Share → change access to 'Anyone with the link can view'. If the iframe still shows blank in production, open the browser developer console (F12) and check for CSP errors in the Console tab. Test in an incognito window to rule out browser extension interference.
Best practices
- Always mark the Tableau PAT secret as a Private parameter in your API Connector Sign In call — even though you exchange it immediately for a session token, the PAT itself should never be visible in the browser network tab.
- Store both the session token AND the site ID UUID in App State after sign-in. The site ID is needed in every subsequent API path and is easy to forget to persist.
- Add a re-authentication workflow on page load that runs every time a user navigates to a Tableau-connected Bubble page. This ensures the session token is always fresh and eliminates silent 4-hour expiry failures.
- Limit who can trigger Tableau workbook refreshes using Bubble's Condition system on the Refresh button — show it only to users with an 'Admin' or 'Analyst' role. Frequent refresh calls may trigger Tableau Server throttles and consume Bubble Workload Units unnecessarily.
- Apply Bubble Privacy Rules to any data type that stores Tableau workbook metadata. If your Bubble app is multi-tenant, users in tenant A should not be able to query workbooks belonging to tenant B.
- Use the Logs tab in Bubble (Settings → Logs → Workflow logs) to debug API call failures. The log shows the full request URL, headers (Private values are redacted), and the raw response — invaluable when troubleshooting 404s caused by wrong site IDs.
- For display-only use cases, prefer the Tableau iframe embed over REST API calls. The embed requires no authentication setup for Tableau Public views and consumes zero Bubble Workload Units, keeping your monthly WU cost lower.
- When the Refresh Workbook job is complete in Tableau, re-run the List Workbooks call to update the 'updatedAt' timestamps in your Bubble Repeating Group. Do not rely on polling — trigger the refresh manually after a reasonable delay.
Alternatives
Power BI also has a REST API for report embedding and dataset refresh, with similar complexity to Tableau's PAT flow. Power BI uses Azure AD OAuth tokens rather than a proprietary PAT exchange, which may integrate more naturally if your team already uses Microsoft 365. Tableau typically has a stronger enterprise BI ecosystem and more data connector variety.
Looker (Google Cloud) offers a more developer-friendly REST API with an embed secret SDK that handles token generation on the server side. If your organization runs on Google Cloud, Looker Studio (free) or Looker (paid) may offer a smoother Bubble integration path. Looker's API is better documented for programmatic access than Tableau's.
Google Analytics Data API (GA4) uses standard OAuth 2.0 and provides query-based access to behavioral data rather than pre-built dashboard embeds. It is significantly easier to set up in Bubble's API Connector than Tableau's stateful PAT flow, but serves a different use case — web traffic and user behavior data rather than business intelligence dashboards built on arbitrary data sources.
Frequently asked questions
Does Bubble expose my Tableau Personal Access Token in the browser?
No. The API Connector sign-in call runs from Bubble's servers. Your PAT is sent as part of the POST body, but because Bubble processes the call server-side, the PAT is never included in a browser network request that a user could inspect. Mark the PAT secret parameter as Private in the API Connector for an additional layer of protection.
Can I connect Bubble to Tableau Server (on-premises) instead of Tableau Cloud?
Yes. Replace the base URL in every API call with your Tableau Server's hostname (e.g., https://tableau.yourcompany.com). The REST API paths and authentication flow are identical. Ensure your Tableau Server's REST API is enabled by your administrator and that Bubble's servers can reach the on-premises URL — some organizations place Tableau Server behind a VPN that blocks external traffic.
Can I embed a private Tableau Cloud report (not Tableau Public) without a sign-in prompt?
Only if the report's sharing setting is 'Anyone with the link can view'. Reports shared with specific users or groups will always show a Tableau/Google sign-in prompt inside the iframe for viewers who are not already authenticated. Tableau's Trusted Authentication feature (server-level configuration) can bypass this, but it requires server-side token generation outside of Bubble's API Connector and is an advanced configuration not covered here.
The workbook refresh POST returns immediately — how do I know when the refresh is done?
Tableau's workbook refresh endpoint returns a job ID in the response, not a completion signal. To check job status you would need to poll GET /api/3.19/sites/{site_id}/jobs/{job_id} repeatedly. Bubble's 30-second workflow timeout makes in-workflow polling impractical. The recommended design is to show 'Refresh requested' to the user and ask them to reload the Tableau workbook view after a few minutes, or use Tableau's built-in scheduled refresh feature for automated updates.
Do I need a paid Bubble plan to build the Tableau integration?
The API Connector calls (sign-in, list workbooks, refresh workbook) work on all Bubble plans. If you want to schedule re-authentication using a Backend Workflow (API Workflow) or schedule periodic workbook list refreshes, that feature requires a paid Bubble plan. For most Bubble apps a page-load sign-in workflow is sufficient and works on the free plan.
My List Workbooks call returns XML instead of JSON even though I set Accept: application/json. What is wrong?
The Accept header must be set at the API group level (Shared headers for this API), not just at the call level, because Tableau checks it on every request. Confirm the header name is exactly 'Accept' (no typos) and the value is exactly 'application/json' (no extra spaces). Also add 'Content-Type: application/json' as a shared header. If XML still appears, check whether your Tableau Server version pre-dates JSON response support — Tableau Server versions before 9.3 return XML only.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation