# Tableau

- Tool: FlutterFlow
- Difficulty: Advanced
- Time required: 40 minutes
- Last updated: July 2026

## TL;DR

Connect FlutterFlow to Tableau by embedding a published Tableau view in a Custom Widget using `flutter_inappwebview` — append `?:embed=y&:showVizHome=no` to the view URL to strip Tableau chrome. For Tableau Cloud with private views, add a Connected Apps JWT authentication layer via a Firebase Cloud Function proxy to avoid a login wall appearing inside your app.

## Embed First: Tableau in FlutterFlow Is About Showing Dashboards, Not Moving Data

The most common reason a FlutterFlow builder wants to 'connect to Tableau' is to show a beautiful, interactive Tableau dashboard inside their mobile or web app. Tableau has invested heavily in an Embedding API and published view URLs precisely for this purpose. A Tableau view published to Tableau Cloud or Tableau Server gets a shareable URL that, with a few query parameters appended, renders cleanly inside a WebView without Tableau's header bar, navigation tabs, or sign-in page.

For public or link-shared Tableau Public views, this is genuinely simple — publish the view, copy the URL, append `?:embed=y&:showVizHome=no`, and point your Custom Widget at it. No authentication, no server, no complexity. Tableau Public is free and a great starting point if you're building a demo or showing publicly available data.

The complexity enters with Tableau Cloud and Tableau Server when views are not publicly shared. Without additional setup, a Tableau Cloud embed in your app will show a Tableau sign-in page — your users will need a Tableau account just to view the dashboard, which defeats the purpose. The solution is Tableau Connected Apps: a JWT-based authentication mechanism where you generate a short-lived signed token server-side that Tableau accepts to auto-authenticate the WebView session. That token generation requires a Connected App secret from your Tableau Cloud site, which must never appear in your compiled Flutter app.

The Tableau REST API (listing workbooks, getting view metadata) follows a similar pattern: a Personal Access Token (PAT) exchanges for a short-lived session token (`X-Tableau-Auth`). That exchange belongs in a Firebase Cloud Function, not in an API Call header in FlutterFlow. Tableau Cloud pricing runs from $15/month per Viewer to $75/month per Creator (check Tableau's current pricing as it changes).

## Before you start

- A Tableau Cloud, Tableau Server, or Tableau Public account with at least one published view you want to embed
- The published view URL from Tableau (from the Share button → Copy Link on the view page)
- A FlutterFlow project — no Firebase required for Tableau Public embeds; Firebase required for the Connected Apps authentication path
- For private Tableau Cloud embeds: a Connected App configured in your Tableau Cloud site (Site Settings → Connected Apps → New Connected App)
- For the REST API path: a Personal Access Token (PAT) from Tableau Cloud (Account Settings → Personal Access Tokens)

## Step-by-step guide

### 1. Publish the Tableau View and Prepare the Embed URL

Before building anything in FlutterFlow, you need a published Tableau view and a properly formatted embed URL. If you're using Tableau Public, go to the view on public.tableau.com, click the 'Share' button at the bottom of the viz, and copy the link URL — it will look like `https://public.tableau.com/views/YourWorkbookName/YourViewName`.

For Tableau Cloud, open the published view in your browser. The URL in the address bar is the view URL — it will look like `https://us-east-1.online.tableau.com/t/your-site/views/WorkbookName/ViewName`. Copy this base URL.

Now append the embedding parameters to strip Tableau's chrome. Add `?:embed=y&:showVizHome=no&:toolbar=no` to the URL. The `embed=y` parameter tells Tableau to render in embedding mode (hides the top navigation bar). `showVizHome=no` prevents Tableau from redirecting to the home page. `toolbar=no` hides the Tableau toolbar at the bottom of the view. Your final embed URL should look like:
`https://public.tableau.com/views/WorkbookName/ViewName?:embed=y&:showVizHome=no&:toolbar=no`

Test this URL in your desktop browser before building the Custom Widget — open a private/incognito window (to simulate a user without a Tableau session) and paste the URL. If you see the Tableau view without a navigation bar or toolbar, the URL is correct. If you see a login page, the view's sharing settings need to be changed to 'Anyone with the link' (for Tableau Cloud) or the URL is missing the embed parameters.

For Tableau Public views (which are always public), this incognito test will always work with no authentication needed. For Tableau Cloud private views, the login wall you see in the incognito test is exactly what your app users will see — the Connected Apps setup in Step 3 solves this.

**Expected result:** You have a tested embed URL that renders the Tableau view without the navigation bar in a private browser window. Public Tableau URLs work immediately; Tableau Cloud private URLs show the login page (which Steps 3-4 will fix).

### 2. Build the InAppWebView Custom Widget in FlutterFlow

Custom Widgets in FlutterFlow let you embed any Dart/Flutter widget that isn't natively available in the visual builder. The `flutter_inappwebview` package provides a high-fidelity WebView that can render full Tableau visualizations including JavaScript-heavy interactive charts.

In your FlutterFlow project, go to the left nav and click 'Custom Code' (the code bracket icon). Click '+ Add' and select 'Widget'. Name it 'TableauEmbed'. In the Dependencies field, add `flutter_inappwebview: ^6.0.0` — this is the pub.dev package name and version. FlutterFlow will handle adding it to the project's pubspec.yaml automatically.

In the Parameters section, add one parameter named `viewUrl` of type String — this is the Tableau embed URL you'll pass in from the page. You can also add optional parameters for `widgetWidth` (double) and `widgetHeight` (double) to control the widget's size.

Paste the Dart widget code (see code block) into the widget editor. The code creates an `InAppWebView` widget that loads your Tableau embed URL with JavaScript enabled — JavaScript is required for Tableau's visualization engine to run.

Save the custom widget. Now go to a screen where you want to show the Tableau view. In the widget tree, click the '+' to add a widget, search for 'TableauEmbed', and drag it onto the screen. In its settings panel, set the `viewUrl` parameter to your Tableau embed URL — either hardcoded or bound to a page variable. Set the widget's width to fill the container and adjust the height to your preferred aspect ratio (Tableau views typically work well at 16:9 or 4:3 aspect ratios).

Critical: Custom Widgets do not render in FlutterFlow's web Run mode preview. When you click 'Run' in the builder, the widget area will appear blank or show a placeholder. This is normal — you must use Test Mode (build and install on a physical device) to see the Tableau view render correctly.

```
// FlutterFlow Custom Widget: TableauEmbed
// Dependencies: flutter_inappwebview: ^6.0.0
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';

class TableauEmbed extends StatefulWidget {
  const TableauEmbed({
    Key? key,
    this.width,
    this.height,
    required this.viewUrl,
  }) : super(key: key);

  final double? width;
  final double? height;
  final String viewUrl;

  @override
  State<TableauEmbed> createState() => _TableauEmbedState();
}

class _TableauEmbedState extends State<TableauEmbed> {
  bool _isLoading = true;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: widget.width ?? double.infinity,
      height: widget.height ?? 400,
      child: Stack(
        children: [
          InAppWebView(
            initialUrlRequest: URLRequest(
              url: WebUri(widget.viewUrl),
            ),
            initialSettings: InAppWebViewSettings(
              javaScriptEnabled: true,
              domStorageEnabled: true,
              allowsInlineMediaPlayback: true,
              mediaPlaybackRequiresUserGesture: false,
            ),
            onLoadStart: (_, __) {
              setState(() => _isLoading = true);
            },
            onLoadStop: (_, __) {
              setState(() => _isLoading = false);
            },
          ),
          if (_isLoading)
            const Center(
              child: CircularProgressIndicator(),
            ),
        ],
      ),
    );
  }
}
```

**Expected result:** The TableauEmbed custom widget appears in the FlutterFlow widget picker. On a real device test build, it renders the Tableau view with the correct embed parameters and shows a loading indicator while the viz initializes.

### 3. Configure Connected Apps JWT Authentication for Private Tableau Cloud Views

If your Tableau views are on Tableau Cloud and not publicly shared, users of your FlutterFlow app will see a Tableau login page inside the WebView. Connected Apps is Tableau Cloud's solution for seamless embedding authentication — you generate a short-lived JWT signed with a Connected App secret, and Tableau auto-authenticates the WebView session without requiring a Tableau user account.

First, set up a Connected App in Tableau Cloud: go to your site → Site Settings → Connected Apps → New Connected App. Choose 'Direct Trust', give it a name, and enable it. Copy the Client ID and generate a Secret — you'll use both in your Cloud Function. Store the secret in Firebase environment configuration or Secret Manager, never in code.

Deploy a Firebase Cloud Function that:
1. Accepts a request from your FlutterFlow app with the user's identifier
2. Generates a JWT signed with the Connected App secret (using the `jsonwebtoken` npm package)
3. Returns a trusted embed URL: your Tableau view URL with the JWT token appended as `?:jwtToken=<token>`

In FlutterFlow, create an API Group pointing at this Cloud Function. The API Call returns the trusted URL from the function. Before loading the TableauEmbed widget on your page, set up an On Page Load action that calls the Cloud Function API Call first, stores the returned URL in a page variable, and then the widget binds its `viewUrl` parameter to that variable.

With Connected Apps configured, the WebView silently authenticates the Tableau session using the JWT, and users see the dashboard directly — no Tableau login prompt.

```
// Firebase Cloud Function: Generate Tableau Connected Apps JWT
// functions/index.js
const functions = require('firebase-functions');
const jwt = require('jsonwebtoken');
const { v4: uuidv4 } = require('uuid');

// Store these in Firebase environment config, not hardcoded
// firebase functions:config:set tableau.client_id="..." tableau.client_secret="..." tableau.site_name="..."
const TABLEAU_CLIENT_ID = process.env.TABLEAU_CLIENT_ID;
const TABLEAU_CLIENT_SECRET = process.env.TABLEAU_CLIENT_SECRET;
const TABLEAU_SITE_NAME = process.env.TABLEAU_SITE_NAME; // your Tableau site URL name
const TABLEAU_HOST = 'https://us-east-1.online.tableau.com'; // your Tableau Cloud host

exports.getTableauEmbedToken = functions.https.onRequest(async (req, res) => {
  res.set('Access-Control-Allow-Origin', '*');
  if (req.method === 'OPTIONS') {
    res.set('Access-Control-Allow-Methods', 'POST');
    res.set('Access-Control-Allow-Headers', 'Content-Type');
    return res.status(204).send('');
  }

  const { viewPath, userEmail } = req.body;

  if (!viewPath) {
    return res.status(400).json({ error: 'viewPath is required' });
  }

  const now = Math.floor(Date.now() / 1000);
  const payload = {
    iss: TABLEAU_CLIENT_ID,
    exp: now + 300, // 5 minutes
    jti: uuidv4(),
    aud: 'tableau',
    sub: userEmail || 'anonymous',
    scp: ['tableau:views:embed', 'tableau:metrics:embed']
  };

  const token = jwt.sign(payload, TABLEAU_CLIENT_SECRET, {
    algorithm: 'HS256',
    header: { kid: TABLEAU_CLIENT_ID, iss: TABLEAU_CLIENT_ID }
  });

  const baseViewUrl = `${TABLEAU_HOST}/t/${TABLEAU_SITE_NAME}/views/${viewPath}`;
  const embedUrl = `${baseViewUrl}?:embed=y&:showVizHome=no&:toolbar=no&:jwtToken=${token}`;

  return res.json({ embedUrl });
});
```

**Expected result:** When your FlutterFlow app calls the Cloud Function and uses the returned embed URL in the TableauEmbed widget, Tableau Cloud views load without showing the login page on a real device test build.

### 4. (Optional) List Tableau Workbooks via REST API Through a Proxy

If you want your FlutterFlow app to show a dynamic list of available Tableau views — letting users pick which dashboard to view — you need the Tableau REST API. This is an advanced optional step that most apps won't need.

The Tableau REST API authentication flow is a two-step process: first, POST to `/api/3.x/auth/signin` with your Personal Access Token name and secret to receive a short-lived `X-Tableau-Auth` session token. Then use that session token in subsequent API calls to list workbooks, views, or download data.

Because a Personal Access Token is a long-lived credential, it must never appear in a FlutterFlow API Call header — it belongs in a Firebase Cloud Function. The Cloud Function holds the PAT, exchanges it for a session token, makes the Tableau REST API calls, and returns the clean workbook/view list as JSON to your FlutterFlow API Call.

In FlutterFlow, create an API Group pointing to this Cloud Function. The API Call returns a list of view names and paths. Bind this list to a Dropdown widget or List widget on your screen. When the user selects a view, set a page variable to the selected path, call the JWT token Cloud Function with that path, and load the TableauEmbed widget with the resulting URL.

Note: the REST API version (3.x in the URL) varies by Tableau Server version. Using the wrong version path returns a 404. Check your Tableau Cloud or Server version in the API documentation to confirm the correct version number.

```
// Firebase Cloud Function: List Tableau views via REST API
// functions/index.js (add this function)
const axios = require('axios');

const TABLEAU_PAT_NAME = process.env.TABLEAU_PAT_NAME;
const TABLEAU_PAT_SECRET = process.env.TABLEAU_PAT_SECRET;
const TABLEAU_HOST = process.env.TABLEAU_HOST;
const TABLEAU_SITE_ID = process.env.TABLEAU_SITE_ID;

exports.listTableauViews = functions.https.onRequest(async (req, res) => {
  res.set('Access-Control-Allow-Origin', '*');
  if (req.method === 'OPTIONS') {
    res.set('Access-Control-Allow-Methods', 'GET');
    res.set('Access-Control-Allow-Headers', 'Content-Type');
    return res.status(204).send('');
  }

  try {
    // Step 1: Authenticate with PAT to get session token
    const authResponse = await axios.post(
      `${TABLEAU_HOST}/api/3.19/auth/signin`,
      {
        credentials: {
          personalAccessTokenName: TABLEAU_PAT_NAME,
          personalAccessTokenSecret: TABLEAU_PAT_SECRET,
          site: { contentUrl: TABLEAU_SITE_ID }
        }
      },
      { headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' } }
    );

    const authToken = authResponse.data.credentials.token;
    const siteId = authResponse.data.credentials.site.id;

    // Step 2: List views
    const viewsResponse = await axios.get(
      `${TABLEAU_HOST}/api/3.19/sites/${siteId}/views`,
      { headers: { 'X-Tableau-Auth': authToken, 'Accept': 'application/json' } }
    );

    const views = viewsResponse.data.views.view.map(v => ({
      id: v.id,
      name: v.name,
      contentUrl: v.contentUrl,
      workbook: v.owner?.name || ''
    }));

    return res.json({ views });
  } catch (err) {
    console.error('Tableau REST API error:', err.response?.data || err.message);
    return res.status(502).json({ error: err.message });
  }
});
```

**Expected result:** A Dropdown or List widget in FlutterFlow populates with view names from your Tableau Cloud site, and selecting one loads that view in the TableauEmbed Custom Widget.

## Best practices

- Start with Tableau Public for demos and proof-of-concept — it requires zero authentication and confirms the embed works before investing time in Connected Apps setup.
- Always test Custom Widget embeds on a real physical device using Test Mode — the FlutterFlow web Run preview does not render InAppWebView-based widgets.
- Never store a Tableau Personal Access Token or Connected App secret in your FlutterFlow API Call headers or Dart code — always handle authentication in a Firebase Cloud Function.
- Append `?:embed=y&:showVizHome=no&:toolbar=no` to all Tableau embed URLs to strip Tableau's navigation chrome and deliver a native-feeling experience inside your app.
- Set a fixed pixel height on the TableauEmbed Custom Widget rather than using an Expanded or unbounded container — InAppWebView requires explicit sizing constraints.
- For user-personalized dashboards, generate a new Connected Apps JWT on each page load and pass user-specific filter values in the JWT payload's claims.
- Monitor your Tableau Cloud Creator/Viewer seat count — each user viewing an embedded dashboard from an application must have a Tableau license (Viewer at minimum) unless you use the Guest user embedding path.

## Use cases

### Executive dashboard screen showing live business metrics in a Tableau view

A FlutterFlow company app displays a Tableau dashboard with revenue, conversion, and pipeline metrics on a dedicated 'Dashboard' screen. The Tableau view connects to a live Salesforce data source, so the chart always shows current data. The FlutterFlow Custom Widget embeds the view with Chrome stripped, giving it a native app feel without rebuilding the visualization.

Prompt example:

```
Build a Dashboard screen with a full-screen Tableau view embedded using InAppWebView, showing a revenue and pipeline report from our Tableau Cloud site, with loading indicator while the view initializes.
```

### Customer portal with a personalized Tableau report per user

A B2B SaaS FlutterFlow app shows each customer a Tableau view filtered to their account data. The FlutterFlow app calls a Firebase Cloud Function that generates a Connected Apps JWT with a user filter applied, returns the trusted embed URL, and the Custom Widget loads the personalized view — no Tableau login required for the end user.

Prompt example:

```
Create a Reports screen that loads a Tableau view filtered to the current user's company using a Connected Apps JWT token from our Cloud Function, embedded in an InAppWebView Custom Widget.
```

### Tableau Public data visualization embedded in a public info app

A non-profit FlutterFlow app shows public data dashboards from Tableau Public — census data, health statistics, or environmental metrics — embedded directly with a static URL and no authentication. This is the zero-backend path: publish the Tableau Public view, copy the embed URL, build the Custom Widget, and ship.

Prompt example:

```
Add a 'Statistics' screen that shows our published Tableau Public dashboard embedded in a scrollable view, with the Tableau toolbar and navigation hidden.
```

## Troubleshooting

### The Tableau embed shows a login page inside the app instead of the dashboard

Cause: The Tableau view is not publicly shared, and the WebView has no authenticated Tableau session. Tableau Cloud requires Connected Apps JWT authentication for non-public views embedded in external applications.

Solution: Set up a Connected App in Tableau Cloud site settings, deploy the JWT-generation Cloud Function from Step 3, and update your Custom Widget's viewUrl to use the token-bearing embed URL returned by the function. For Tableau Public views, ensure the URL domain is `public.tableau.com` and the view's sharing is set to public.

### The TableauEmbed Custom Widget is blank or invisible in FlutterFlow's Run mode preview

Cause: Custom Widgets using InAppWebView do not render in FlutterFlow's web Run mode preview. This is a known limitation — the web preview environment does not support the full WebView rendering pipeline.

Solution: Use FlutterFlow's Test Mode to build and install the app on a physical iOS or Android device. The Tableau embed will render correctly in a Test Mode build. This is not a bug in your code — it is the expected behavior for any Custom Widget using flutter_inappwebview in FlutterFlow's preview environment.

### iOS build fails with 'App Transport Security' error or Tableau URL is blocked

Cause: iOS App Transport Security (ATS) blocks HTTP (non-HTTPS) connections by default. If your Tableau Server URL uses HTTP rather than HTTPS, iOS will block the WebView from loading it.

Solution: Ensure your Tableau Cloud or Server URL uses HTTPS. Tableau Cloud (online.tableau.com) is always HTTPS. For self-hosted Tableau Server, configure a valid TLS certificate. As a temporary workaround during development, you can add an ATS exception in FlutterFlow's iOS settings for your specific domain, but shipping with ATS exceptions is not recommended for production apps.

### Tableau REST API returns 404 with a 'Not found' response from the Cloud Function

Cause: The API version number in the URL path (e.g., `/api/3.19/`) does not match the Tableau Server or Cloud version you're using. Each Tableau version supports a specific set of REST API versions.

Solution: Check your Tableau Cloud site's REST API version compatibility in the Tableau REST API documentation for your specific product version. Update the version number in the Cloud Function URL from `3.x` to the correct version for your installation. Tableau Cloud typically supports recent API versions, while older on-premise servers may require older version numbers.

## Frequently asked questions

### Can I embed a Tableau view without a Tableau account or license?

For Tableau Public views, yes — anyone can embed them for free since the views are publicly available. For Tableau Cloud or Server views, your organization needs active Tableau licenses (at minimum Viewer seats). The end users of your FlutterFlow app viewing embedded dashboards typically need a Viewer license on your Tableau Cloud site unless you configure specific Guest access settings.

### Why does the Tableau embed work in my browser but show a login screen inside the app?

Your browser session has a cached Tableau authentication cookie from logging into Tableau Cloud previously. The FlutterFlow app starts with a fresh WebView with no cookies, so it has no authenticated session and falls back to showing the login page. This is why Connected Apps JWT authentication is necessary for private Tableau Cloud views — it provides a signed token that authenticates the WebView session without relying on cookies.

### Can I filter the Tableau view based on the current user's data in FlutterFlow?

Yes, there are two approaches. The simpler one is URL filter parameters: append `?YourFieldName=YourValue&:embed=y` to the embed URL, where `YourFieldName` matches a field in your Tableau data source. The more secure approach is using Connected Apps JWT User Attributes, where you pass user-specific values in the JWT payload claims and configure Tableau to apply them as data filters. The second approach prevents users from modifying the URL to see other users' data.

### Does the Tableau embed work on both iOS and Android in FlutterFlow?

Yes — the `flutter_inappwebview` package supports both iOS and Android, and Tableau's JavaScript-based visualizations render correctly in the WebView on both platforms. iOS requires HTTPS URLs (App Transport Security blocks HTTP). Test on both platforms before releasing, as rendering performance can vary between iOS WKWebView and Android WebView.

### What happens if the Tableau Connected Apps JWT expires while the user is viewing the dashboard?

The JWT is used to establish the WebView session when the page loads. Once the session is established, the token is no longer actively validated — the Tableau session persists independently. A 5-minute JWT expiry means the token only needs to be valid at the moment the WebView first loads. For long app sessions where users navigate away and back, re-fetching a fresh JWT each time the screen loads is the safest approach.

---

Source: https://www.rapidevelopers.com/flutterflow-integrations/tableau
© RapidDev — https://www.rapidevelopers.com/flutterflow-integrations/tableau
