# Metabase Embedding - Complete Reference for AI agents > **This documentation is for Metabase 59 (latest).** > > Table of contents: https://metabase.com/docs/latest/llms.txt ## IMPORTANT: Verify SDK and Metabase Version Compatibility The SDK version MUST match the Metabase instance version. Mismatched versions can cause errors. When looking up documentation, ALWAYS check the Metabase version. **Step 1: Ask the user for their Metabase instance URL** Before proceeding, ask the user where their Metabase instance is located. Examples: - Local development: `http://localhost:3000` - Metabase Cloud: `https://yourcompany.metabaseapp.com` - Self-hosted: `https://metabase.yourcompany.com` **Step 2: Check if SDK is already installed (React SDK / Modular Embedding only)** Skip this step if not using the React SDK (`@metabase/embedding-sdk-react`). ```bash npm list @metabase/embedding-sdk-react ``` If installed, note the version (e.g., `0.58.0` means this is for Metabase 58). **Step 3: Query the Metabase instance version** Using the URL from Step 1: ```bash curl /api/session/properties | jq .version ``` This returns (no authentication required): ```json { "date": "2025-01-10", "tag": "v1.58.0", "hash": "8e44dd8" } ``` If `jq` is not installed, you can grep the version. Extract the major version: `58` from `v1.58.x` or `v0.58.x`. **Step 4: Ensure versions match** - If the versions mismatch, you MUST fetch the version-specific llms.txt documentation that matches the Metabase instance version: `https://metabase.com/docs/v0.{VERSION}/llms.txt` (e.g., `/docs/v0.58/llms.txt` for Metabase 58) - For React SDK, ask the user to install or update their SDK packages if they are mismatched: `npm install @metabase/embedding-sdk-react@{VERSION}-stable` (e.g., `@58-stable` for Metabase 58) **Do NOT guess versions or use versions from your training data. Always verify first.** ## Modular Embedding Deprecations and Gotchas Watch out for these deprecated props and gotchas for Metabase 57 onwards, for modular embedding. 1. `config` prop on MetabaseProvider no longer exist as it is replaced by `authConfig`. 2. `authProviderUri` field no longer exist. 3. `jwtProviderUri` is an optional field that only exists in v58+. This is used to make JWT auth faster by skipping the `GET /auth/sso` discovery request. This field is not required for the initial implementation. 4. Numeric IDs must be integers not strings, e.g. `dashboardId={1}`. When the ID is retrieved from the router as a string AND it is numeric, `parseInt` it before passing it to the SDK. 5. IDs can also be strings for entity IDs, so you should NOT parse all IDs as numbers if entity IDs are also to be expected. 6. `fetchRequestToken` is not needed by default in most implementations. This is only used to customize how the SDK fetches the request token. For example, if the `/sso/metabase` endpoint in the user's backend requires passing custom auth tokens or headers. 7. When using `fetchRequestToken`, you MUST return the token in the shape of `{jwt: ""}`. Example: `return {jwt: await response.json()}`. # AI agent resources for embedding If you use an AI coding agent, you can give the agent Metabase-specific context to help with embedding setup, upgrades, and migrations. ## Agent skills We've developed some [agent skills](https://github.com/metabase/agent-skills) to give your AI agents step-by-step playbooks for specific embedding tasks. ### Available skills | Skill | Description | | ------------------------------- | -------------------------------------------------------------------------------------------- | | [SDK version upgrade](https://skillsmp.com/skills/metabase-agent-skills-skills-metabase-modular-embedding-version-upgrade-skill-md) | Upgrade your modular embedding SDK, including changelog checks and breaking change handling. | | [Full app → modular embedding](https://skillsmp.com/skills/metabase-agent-skills-skills-metabase-full-app-to-modular-embedding-upgrade-skill-md) | Migrate from full app embedding to modular embedding. | | [Modular embedding → SDK (React)](https://skillsmp.com/skills/metabase-agent-skills-skills-metabase-modular-embedding-to-modular-embedding-sdk-upgrade-skill-md) | Migrate from script-based modular embedding to the React SDK. | | [Static → guest embeds](https://skillsmp.com/skills/metabase-agent-skills-skills-metabase-static-embedding-to-guest-embedding-upgrade-skill-md) | Migrate from static (signed) embeds to guest embeds. | | [SSO for embeds](https://skillsmp.com/skills/metabase-agent-skills-skills-metabase-embedding-sso-implementation-skill-md) | Set up SSO authentication for embedded Metabase. _(In development.)_ | Browse all skills on the [agent skills repo](https://github.com/metabase/agent-skills). ## llms.txt Agents can read the docs you're reading now (and many agents have already read our docs), but we also publish llms.txts so that you can give your AI agent: - [Summary of embedding docs, with links](/docs/llms.txt). - [The full embedding docs](/docs/llms-embedding-full.txt). If you're on a specific version (e.g., v0.58), you can use versioned llms.txt files scoped to that version's docs: - `https://www.metabase.com/docs/v0.58/llms.txt` - `https://www.metabase.com/docs/v0.58/llms-embedding-full.txt` ## Agents are not magic Always review and validate the changes made by agents. Check that your application builds, tests pass, and the embedding works as expected before committing anything. ## Further reading - [Embedding introduction](./introduction) - [Modular embedding SDK](./sdk/introduction) - [Upgrading the modular embedding SDK](./sdk/upgrade) - [Agent skills repo](https://github.com/metabase/agent-skills) --- # Customizing the appearance of modular embeds You can style your embedded [Metabase components](./components) with a **theme**. ![Embed share button](./images/embed-share-button.png) Guest embeds on OSS and Starter plans come with two theme presets - light and dark. On Metabase Pro and Enterprise plans, you can also customize individual colors, backgrounds, fonts and more. See [Advanced theming](#advanced-theming). ## Dark mode and light mode By default, components that you embed using the SDK will use a light mode theme. To use dark mode, set `theme.preset` to `"dark"` in the `defineMetabaseConfig()` function of your [embedding code snippet](./modular-embedding#add-the-embedding-script-to-your-app): ```js defineMetabaseConfig({ theme: { preset: "dark", // or "light" }, }); ``` ## Advanced theming On Pro/Enterprise plan, you can configure granular appearance options, like background colors, font sizes etc. See the [list of all theming options](). ### Add an advanced theme to your embed ![Behavior and appearance](./images/behavior-and-appearance.png) Some appearance options like brand, text, and background color are configurable in in the [embed wizard](./modular-embedding#create-a-new-embed). For other appearance settings, use the `theme` parameter with `preset` in the `defineMetabaseConfig()` function in your [embedding code snippet](./modular-embedding#add-the-embedding-script-to-your-app). For example: ```js defineMetabaseConfig({ theme: { colors: { background: "#FFFFFF", "text-primary": "hsla(204, 66%, 8%, 0.84)", brand: "hsla(208, 72%, 60%, 1.00)", "background-hover": "rgb(236, 236, 236)", "background-disabled": "rgb(231, 231, 231)", "background-secondary": "rgb(233, 233, 233)", "background-light": "rgb(233, 233, 233)", "text-secondary": "rgba(9, 30, 44, 0.84)", "text-tertiary": "rgba(11, 37, 54, 0.84)", "brand-hover": "rgb(185, 216, 244)", "brand-hover-light": "rgb(238, 245, 252)", }, }, instanceUrl: "http://localhost:3000", }); ``` ### Theme options For advanced theme configuration options, you can configure the `theme` object in the object passed to the `defineMetabaseConfig` function, see [Add an advanced theme to your embed](#add-an-advanced-theme-to-your-embed). Here's the list of all options available for customization: ```json { // Specify a font to use from the set of fonts supported by Metabase. // You can set the font to "Custom" to use the custom font // configured in your Metabase instance. "fontFamily": "Lato", // Override the base font size for every component. // This does not usually need to be set, as the components // inherit the font size from the parent container, such as the body. "fontSize": "16px", // Override the base line height for every component. "lineHeight": 1.5, // Match your application's color scheme "colors": { // The primary color of your application "brand": "#9B5966", // Lighter variation of the brand color. Used for hover and accented elements. "brand-hover": "#DDECFA", // Lightest variation of the brand color. Used for hover and accented elements. "brand-hover-light": "#EEF6FC", // The color of text that is most prominent "text-primary": "#4C5773", // The color of text that is less prominent "text-secondary": "#696E7B", // The color of text that is least prominent "text-tertiary": "#949AAB", // Default background color "background": "#FFFFFF", // Light background color for some control backgrounds. // Defaults are derived from `background` (slightly darker in light mode, much lighter in dark mode). "background-light": "#F0F2F5", // Slightly muted background color. "background-secondary": "#EDF2F5", // Slightly darker background color used for hover and accented elements "background-hover": "#F9FBFC", // Muted background color used for disabled elements, such as disabled buttons and inputs. "background-disabled": "#F3F5F7", // Color used for borders "border": "#EEECEC", // Color used for filters context "filter": "#7172AD", // Color used for aggregations and breakouts context "summarize": "#88BF4D", // Color used to indicate successful actions and positive values/trends "positive": "#BADC58", // Color used to indicate dangerous actions and negative values/trends "negative": "#FF7979", /** Color used to outline elements in focus */ "focus": "#CAE1F7", /** Color used for popover shadows */ "shadow": "rgba(0,0,0,0.08)", // Overrides the chart colors. Supports up to 8 colors // Limitation: this does not affect charts with custom series color "charts": [ // can either be a hex code "#9B59B6", // or a color object. tint and shade represents lighter and darker variations // only base color is required, while tint and shade are optional { "base": "#E74C3C", "tint": "#EE6B56", "shade": "#CB4436" } ] }, "components": { // Dashboard "dashboard": { // Background color for all dashboards "backgroundColor": "#2F3640", // Border color of the dashboard grid, shown only when editing dashboards. // Defaults to `colors.border` "gridBorderColor": "#EEECEC", "card": { // Background color for all dashboard cards "backgroundColor": "#2D2D30", // Apply a border color instead of shadow for dashboard cards. // Unset by default. "border": "1px solid #EEECEC" } }, // Question "question": { // Background color for all questions "backgroundColor": "#2E353B", // Toolbar of the default interactive question layout "toolbar": { "backgroundColor": "#F3F5F7" } }, // Tooltips "tooltip": { // Tooltip text color. "textColor": "#FFFFFF", // Secondary text color shown in the tooltip, e.g. for tooltip headers and percentage changes. "secondaryTextColor": "#949AAB", // Tooltip background color. "backgroundColor": "#2E353B", // Tooltip background color for focused rows. "focusedBackgroundColor": "#0A0E10" }, // Data table "table": { "cell": { // Text color of cells, defaults to `text-primary` "textColor": "#4C5773", // Default background color of cells, defaults to `background` "backgroundColor": "#FFFFFF", // Font size of cell values, defaults to ~12.5px "fontSize": "12.5px" }, "idColumn": { // Text color of ID column, defaults to `brand` "textColor": "#9B5966", // Background color of ID column, defaults to a lighter shade of `brand` "backgroundColor": "#F5E9EB" } }, // Number chart "number": { // Value displayed on number charts. // This also applies to the primary value in trend charts. "value": { "fontSize": "24px", "lineHeight": "21px" } }, // Cartesian chart "cartesian": { // Padding around the cartesian charts. // Uses CSS's `padding` property format. "padding": "4px 8px" }, // Pivot table "pivotTable": { "cell": { // Font size of cell values, defaults to ~12px "fontSize": "12px" }, // Pivot row toggle to expand or collapse row "rowToggle": { "textColor": "#FFFFFF", "backgroundColor": "#95A5A6" } }, "collectionBrowser": { "breadcrumbs": { "expandButton": { "textColor": "#8118F4", "backgroundColor": "#767D7C", "hoverTextColor": "#CE8C8C", "hoverBackgroundColor": "#69264B" } } }, // Popover are used in components such as click actions in interactive questions. "popover": { // z-index of the popover. Useful for embedding components in a modal. defaults to 4. "zIndex": 4 } } } ``` ## Limitations - CSS variables aren't yet supported. If you'd like Metabase to support CSS variables, please upvote this [feature request](https://github.com/metabase/metabase/issues/59237). - Colors set in the visualization settings for a question will override theme colors. --- # Modular embedding - authentication For using modular embedding with SSO in production, you'll need to set up authentication. If you're developing locally, you can also set up authentication with [API keys](#authenticating-locally-with-api-keys). You can set up SSO with JWT or SAML. ## Setting up JWT SSO To set up JWT SSO, you'll need [a Metabase Pro or Enterprise license](/pricing/). Here's a high-level overview: 1. [Enable JWT SSO in your Metabase](#1-enable-jwt-sso-in-your-metabase) 2. [Add a new endpoint to your backend to handle authentication](#2-add-a-new-endpoint-to-your-backend-to-handle-authentication) 3. [Wire your frontend to your new endpoint](#3-wire-your-frontend-to-your-new-endpoint) ### 1. Enable JWT SSO in your Metabase 1. Configure JWT by going to **Admin** > **Settings** > **Authentication** and clicking on **JWT** 2. Enter the JWT Identity Provider URI, for example `http://localhost:9090/sso/metabase`. This is a new endpoint you will add in your backend to handle authentication. 3. Generate a key and copy it to your clipboard. ### 2. Add a new endpoint to your backend to handle authentication You'll need to add a library to your backend to sign your JSON Web Tokens. For Node.js, we recommend jsonwebtoken: ``` npm install jsonwebtoken --save ``` Next, set up an endpoint on your backend (e.g., `/sso/metabase`) that uses your Metabase JWT shared secret to generate a JWT for the authenticated user. **This endpoint must return a JSON object with a `jwt` property containing the signed JWT.** For example: `{ "jwt": "your-signed-jwt" }`. This example code for Node.js sets up an endpoint using Express: ```js ``` Example using Next.js App Router: ```typescript ``` Example using Next.js Pages Router: ```typescript ``` #### Handling full app and SDK embeds with the same endpoint If you have an existing backend endpoint configured for full app embedding and want to use the same endpoint for SDK embedding, you can differentiate between the requests by checking for the `response=json` query parameter that the SDK adds to its requests. - For SDK requests, you should return a JSON object with the JWT (`{ jwt: string }`). - For full app embedding requests, you would proceed with the redirect. Here's an example of an Express.js endpoint that handles both: ```typescript ``` ### 3. Wire your frontend to your new endpoint Update the config in your frontend code to point to your backend's authentication endpoint. ```js ``` (Optional) If you use headers instead of cookies to authenticate calls from your frontend to your backend, you'll need to use a [custom fetch function](#customizing-jwt-authentication). ## If your frontend and backend don't share a domain, you need to enable CORS You can add some middleware in your backend to handle cross-domain requests. ```js ``` ## Customizing JWT authentication You can customize how the SDK fetches the request token by specifying the `fetchRequestToken` function with the `defineMetabaseAuthConfig` function: ```typescript ``` The response should be in the form of `{ jwt: "{JWT_TOKEN}" }` ## Setting up SAML SSO To use SAML single sign-on with modular embedding, you'll need to set up SAML in both your Metabase and your Identity Provider (IdP). See the docs on [SAML-based authentication](../people-and-groups/authenticating-with-saml). Once SAML is configured in Metabase and your IdP, you can configure the SDK to use SAML by setting the `preferredAuthMethod` in your `MetabaseAuthConfig` to `"saml"`: ```typescript ``` Using SAML authentication with modular embedding will typically involve redirecting people to a popup with your Identity Provider's login page for authentication. After successful authentication, the person will be redirected back to the embedded content. Due to the nature of redirects and popups involved in the SAML flow, SAML authentication may not work seamlessly in all embedding contexts, particularly within iframes, depending on browser security policies and your IdP's configuration. We recommend testing auth flows in your target environments. Unlike JWT authentication, you won't be able to implement a custom `fetchRequestToken` function on your backend when pairing SAML with modular embedding. ## If both SAML and JWT are enabled, modular embedding will default to SAML You can override this default behavior to prefer the JWT authentication method by setting `preferredAuthMethod="jwt"` in your authentication config: ```typescript authConfig: { metabaseInstanceUrl: "...", preferredAuthMethod: "jwt", // other JWT config... } ``` ## Getting Metabase authentication status You can query the Metabase authentication status using the `useMetabaseAuthStatus` hook. This is useful if you want to completely hide Metabase components when the user is not authenticated. This hook can only be used within components wrapped by `MetabaseProvider`. ```jsx ``` ## Authenticating locally with API keys > Modular embedding only supports JWT authentication in production. Authentication with API keys is only supported for local development and evaluation purposes. For developing locally to try out modular embedding, you can authenticate using an API key. First, create an [API key](../people-and-groups/api-keys). Then you can then use the API key to authenticate with Metabase in your application. All you need to do is include your API key in the config object using the key: `apiKey`. ```typescript ``` ## Security warning: each end-user _must_ have their own Metabase account Each end-user _must_ have their own Metabase account. The problem with having end-users share a Metabase account is that, even if you filter data on the client side via modular embedding, all end-users will still have access to the session token, which they could use to access Metabase directly via the API to get data they're not supposed to see. If each end-user has their own Metabase account, however, you can configure permissions in Metabase and everyone will only have access to the data they should. In addition to this, we consider shared accounts to be unfair usage. Fair usage of modular embedding involves giving each end-user of the embedded analytics their own Metabase account. ## Upgrade guide for JWT SSO setups on SDK version 54 or below If you're upgrading from an SDK version 1.54.x or below and you're using JWT SSO, you'll need to make the following changes. **Frontend changes**: - [Remove `authProviderUri` from all `defineMetabaseAuthConfig` calls](#remove-authprovideruri-from-your-auth-config) - **If using custom `fetchRequestToken`:** [Update function signature and hardcode authentication endpoint URLs](#update-the-fetchrequesttoken-function-signature) **Backend changes**: - [Update backend endpoint to return `{ jwt: "token" }` JSON response for SDK requests](#update-your-jwt-endpoint-to-handle-sdk-requests). Additionally, if you have SAML set up, but you'd prefer to use JWT SSO, you'll need to set a [preferred authentication method](#if-both-saml-and-jwt-are-enabled-modular-embedding-will-default-to-saml). ### Remove `authProviderUri` from your auth config `defineMetabaseAuthConfig` no longer accepts an `authProviderUri` parameter, so you'll need to remove it. **Admin setting changes in Metabase**: In **Admin** > **Authentication** > **JWT SSO**, set the `JWT Identity Provider URI` to the URL of your JWT SSO endpoint, e.g., `http://localhost:9090/sso/metabase`. **Before:** ```jsx const authConfig = defineMetabaseAuthConfig({ metabaseInstanceUrl: "https://your-metabase.example.com", authProviderUri: "http://localhost:9090/sso/metabase", // Remove this line }); ``` **After:** ```jsx const authConfig = defineMetabaseAuthConfig({ metabaseInstanceUrl: "https://your-metabase.example.com", }); ``` The SDK now uses the JWT Identity Provider URI setting configured in your Metabase Admin (Admin > Settings > Authentication > JWT). ### Update the `fetchRequestToken` function signature The `fetchRequestToken` function no longer receives a URL parameter. You must now specify your authentication endpoint directly in the function. **Before:** ```jsx const authConfig = defineMetabaseAuthConfig({ fetchRequestToken: async (url) => { // Remove url parameter const response = await fetch(url, { method: "GET", headers: { Authorization: `Bearer ${yourToken}` }, }); return await response.json(); }, metabaseInstanceUrl: "http://localhost:3000", authProviderUri: "http://localhost:9090/sso/metabase", // Remove this line }); ``` **After:** ```jsx const authConfig = defineMetabaseAuthConfig({ fetchRequestToken: async () => { // No parameters const response = await fetch("http://localhost:9090/sso/metabase", { // Hardcode your endpoint URL method: "GET", headers: { Authorization: `Bearer ${yourToken}` }, }); return await response.json(); }, metabaseInstanceUrl: "http://localhost:3000", }); ``` ### Update your JWT endpoint to handle SDK requests Your JWT endpoint must now handle both SDK requests and full app embedding requests. The SDK adds a `response=json` query parameter to distinguish its requests. For SDK requests, return a JSON object with the JWT. For full app embedding, continue redirecting as before. If you were using a custom `fetchRequestToken`, you'll need to update the endpoint to detect `req.query.response === "json"` for SDK requests. ```jsx app.get("/sso/metabase", async (req, res) => { // SDK requests include 'response=json' query parameter const isSdkRequest = req.query.response === "json"; const user = getCurrentUser(req); const token = jwt.sign( { email: user.email, first_name: user.firstName, last_name: user.lastName, groups: [user.group], exp: Math.round(Date.now() / 1000) + 60 * 10, }, METABASE_JWT_SHARED_SECRET, ); if (isSdkRequest) { // For SDK requests, return JSON object with jwt property res.status(200).json({ jwt: token }); } else { // For full app embedding, redirect as before const ssoUrl = `${METABASE_INSTANCE_URL}/auth/sso?token=true&jwt=${token}`; res.redirect(ssoUrl); } }); ``` ## Embedding Metabase in a different domain This section applies only to **authenticated embeds**. Guest embeds work cross-domain without additional configuration. If you want to embed Metabase in another domain (say, if Metabase is hosted at `metabase.yourcompany.com`, but you want to embed Metabase at `yourcompany.github.io`), you'll need to [allow your domain in CORS](#allow-your-domain-in-cors). ### Allow your domain in CORS Go to **Admin** > **Embedding** > **Modular embedding** and add your embedding domain under **Cross-Origin Resource Sharing (CORS)** (such as `https://*.example.com`). ### Configure session cookies when testing locally When you use `useExistingUserSession: true` during development on a different domain, the browser must send the existing Metabase session cookie cross-origin into the iframe. To allow this, you'll need to set the session cookie's SameSite value to "none". You can set session cookie's SameSite value in **Admin** > **Embedding** > **Security** > **SameSite cookie setting**. SameSite values include: - **Lax** (default): Allows Metabase session cookies to be shared on the same domain. Used for production instances on the same domain. - **None (requires HTTPS)**: Use "None" when your app and Metabase are hosted on different domains. Incompatible with Safari and iOS-based browsers. - **Strict** (not recommended): Does not allow Metabase session cookies to be shared with embedded instances. Use this if you do not want to enable session sharing with embedding. You can also set the [`MB_SESSION_COOKIE_SAMESITE` environment variable](../configuring-metabase/environment-variables#mb_session_cookie_samesite). If you're using Safari, you'll need to [allow cross-site tracking](https://support.apple.com/en-tj/guide/safari/sfri40732/mac). Depending on the browser, you may also run into issues when viewing embedded items in private/incognito tabs. Learn more about [SameSite cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite). --- # Modular embedding components There are different components you can embed, each with various options. > While you can use component parameters to show or hide parts of the embedded component, these parameters are _not_ a substitute for [permissions](../permissions/start). Even if you hide stuff, people could still grab their token from the frontend and use it to query the Metabase API. This page covers what you can embed. For theming your embeds, see [Appearance](./appearance). > Depending on the framework you're using, you may need to stringify attributes before passing them to the embedded components. ## Dashboard To render a dashboard: ```html ``` ### Attributes For all modular embeds, you can also set a `locale` in your page-level configuration to [translate embedded content](./translations), including content from translation dictionaries. If you surround your attribute value with double quotes, make sure to use single quotes: ```html ``` If you surround your attribute value with double quotes, make sure to use single quotes: ```html ``` ## Resizing dashboards to fit their content The `` web component automatically resizes to fit its content. No additional configuration is needed. ## Question To render a question (chart): ```html ``` ### Attributes ## Browser Browser component is only available for authenticated modular embeds. It's unavailable for [Guest embeds](./guest-embedding). To render a collection browser so people can navigate a collection and open dashboards or questions: ```html ``` ### Attributes ## AI chat AI chat component is only available for authenticated modular embeds. It's unavailable for [Guest embeds](./guest-embedding). To render the AI chat interface: ```html ``` ### Attributes ## Customizing loader and error components If you're using the [modular embedding SDK](./sdk/introduction), you can provide your own components for loading and error states by specifying `loaderComponent` and `errorComponent` as props to `MetabaseProvider`. ```tsx ``` ## Further reading - [Appearance](./appearance) - [Modular embedding SDK](./sdk/introduction). --- Attributes for the `` web component. Embeds a collection browser so people can navigate collections and open dashboards or questions. Only available for authenticated (SSO) modular embeds. ## Remarks Pro/Enterprise ## Properties | Property | Type | Description | | :------------------------------------------------------------------- | :------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `collection-entity-types` | `string`[] | An array of entity types to show in the collection browser: `collection`, `dashboard`, `question`, `model`.
---
Optional
Possible values: `"model"`, `"collection"`, `"dashboard"`, `"question"` | | `collection-page-size` | `number` | How many items to show per page in the collection browser.
---
Optional | | `collection-visible-columns` | `string`[] | An array of columns to show in the collection browser: `type`, `name`, `description`, `lastEditedBy`, `lastEditedAt`, `archive`.
---
Optional
Possible values: `"type"`, `"name"`, `"description"`, `"lastEditedBy"`, `"lastEditedAt"`, `"archive"` | | `data-picker-entity-types` | `string`[] | An array of entity types to show in the question's data picker: `model`, `table`.
---
Optional
Possible values: `"model"`, `"table"` | | `enable-entity-navigation` | `boolean` | Whether to enable internal entity navigation (links to dashboards/questions).
---
Optional
Default: `false` | | `initial-collection` | `string` \| `number` | Which collection to start from. Use a collection ID (e.g., `14`) to start in a specific collection, or `"root"` for the top-level "Our Analytics" collection. | | `read-only` | `boolean` | Whether the content manager is in read-only mode. When `true`, people can interact with items (filter, summarize, drill-through) but can't save. When `false`, they can create and edit items.
---
Optional
Default: `true` | | `with-new-dashboard` | `boolean` | Whether to show the "New dashboard" button. Only applies when `read-only` is `false`.
---
Optional
Default: `true` | | `with-new-question` | `boolean` | Whether to show the "New exploration" button.
---
Optional
Default: `true` | --- Attributes for the `` web component. Embeds a Metabase dashboard. Provide either `dashboard-id` (for SSO embeds) or `token` (for guest embeds), plus optional display configuration. ## Properties | Property | Type | Description | | :--------------------------------------------------------------- | :------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `auto-refresh-interval` | `number` | Auto-refresh interval in seconds. For example, `60` refreshes the dashboard every 60 seconds.
---
Optional
Available in Pro/Enterprise and Guest embed. | | `dashboard-id` | `string` \| `number` | The ID of the dashboard to embed. Can be a regular ID or an [entity ID](/docs/latest/installation-and-operation/serialization#entity-ids-work-with-embedding). Only for SSO embeds — guest embeds set the ID with `token`. | | `drills` | `boolean` | Whether to enable drill-through on the dashboard.
---
Optional
Default: `true`
Available in Pro/Enterprise. | | `enable-entity-navigation` | `boolean` | Whether to enable internal entity navigation (links to dashboards/questions). Requires `drills` to be `true`
---
Optional
Default: `false`
Available in Pro/Enterprise. | | `hidden-parameters` | `string`[] | List of filter names to hide from the dashboard, e.g. `['productId']`.
---
Optional
Available in Pro/Enterprise. | | `initial-parameters` | `object` | Default values for dashboard filters, e.g. `{ 'productId': '42' }`.
---
Optional
Available in Pro/Enterprise and Guest embed. | | `token` | `string` | The token for guest embeds. Set automatically by the guest embed flow.
---
Optional
Available in Guest embed. | | `with-downloads` | `boolean` | Whether to show the button to download the dashboard as PDF and download question results.
---
Optional
Default: `true` on OSS/Starter, `false` on Pro/Enterprise
Available in Guest embed. | | `with-subscriptions` | `boolean` | Whether to let people set up [dashboard subscriptions](/docs/latest/dashboards/subscriptions). Subscriptions sent from embedded dashboards exclude links to Metabase items.
---
Optional
Available in Pro/Enterprise. | | `with-title` | `boolean` | Whether to show the dashboard title in the embed.
---
Optional
Default: `true`
Available in Guest embed. | --- Attributes for the `` web component. Embeds the AI chat interface. Only available for authenticated (SSO) modular embeds. ## Remarks Pro/Enterprise ## Properties | Property | Type | Description | | :------------------------------------------------- | :------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `is-save-enabled` | `boolean` | Whether the save button is enabled.
---
Optional
Default: `false` | | `layout` | `string` | How should the browser position the visualization with respect to the chat interface. `auto` uses `stacked` on mobile and `sidebar` on larger screens.
---
Optional
Default: `"auto"`
Possible values: `"auto"`, `"sidebar"`, `"stacked"` | | `target-collection` | `string` \| `number` | The collection to save a question to.
---
Optional | --- Attributes for the `` web component. Embeds a Metabase question (chart). Provide either `question-id` (for SSO embeds) or `token` (for guest embeds), plus optional display configuration. Use `question-id="new"` to embed the query builder exploration interface. Use `question-id="new-native"` to embed the SQL editor interface. ## Properties | Property | Type | Description | | :----------------------------------------------------------- | :------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `drills` | `boolean` | Whether to enable drill-through on the question.
---
Optional
Default: `true`
Available in Pro/Enterprise. | | `entity-types` | `string`[] | Which entity types to show in the question's data picker, e.g. `["model", "table"]`.
---
Optional
Possible values: `"model"`, `"table"`
Available in Pro/Enterprise and Guest embed. | | `hidden-parameters` | `string`[] | List of parameter names to hide from the question.
---
Optional
Available in Pro/Enterprise. | | `initial-sql-parameters` | `object` | Default values for SQL parameters, only applicable to native SQL questions, e.g. `{ "productId": "42" }`.
---
Optional
Available in Pro/Enterprise and Guest embed. | | `is-save-enabled` | `boolean` | Whether the save button is enabled.
---
Optional
Default: `false`
Available in Pro/Enterprise. | | `question-id` | `string` \| `number` | The ID of the question to embed. Can be a regular ID or an [entity ID](/docs/latest/installation-and-operation/serialization#entity-ids-work-with-embedding). Use `"new"` to embed the query builder. Only for SSO embeds — guest embeds use `token`. | | `target-collection` | `string` \| `number` | The collection to save a question to. Values: regular ID, entity ID, `"personal"`, `"root"`.
---
Optional
Available in Pro/Enterprise. | | `token` | `string` | The token for guest embeds. Set automatically by the guest embed flow.
---
Optional
Available in Guest embed. | | `with-alerts` | `boolean` | Whether to show the alerts button.
---
Optional
Default: `false`
Available in Pro/Enterprise. | | `with-downloads` | `boolean` | Whether to show download buttons for question results.
---
Optional
Default: `true` on OSS/Starter, `false` on Pro/Enterprise
Available in Guest embed. | | `with-title` | `boolean` | Whether to show the question title in the embed.
---
Optional
Default: `true`
Available in Guest embed. | --- Types for modular embedding web-components. This is the source of truth for all attributes accepted by modular embedding components. ## Interfaces | Interface | Description | | :------------------------------------------------------------ | :------------------------------------------------------- | | [MetabaseBrowserAttributes](MetabaseBrowserAttributes) | Attributes for the `` web component. | | [MetabaseDashboardAttributes](MetabaseDashboardAttributes) | Attributes for the `` web component. | | [MetabaseMetabotAttributes](MetabaseMetabotAttributes) | Attributes for the `` web component. | | [MetabaseQuestionAttributes](MetabaseQuestionAttributes) | Attributes for the `` web component. | --- # Full app embedding quickstart > If you are just starting out with Metabase embedding, consider using [Modular embedding](./modular-embedding) - an improved, more customizable option for embedding Metabase components. You'll embed the full Metabase application in your app. Once logged in, people can view a Metabase dashboard in your web app, and be able to use the full Metabase application to explore their data, and only their data. ## Prerequisites - You have an app that you can embed Metabase in. - You have a Pro or Enterprise subscription of Metabase. If you're unsure where to start, sign up for a free trial for [Pro On-Prem](https://store.metabase.com/checkout/embedding). If you have Docker Desktop installed, you can just search for "metabase-enterprise" to find the Docker image and run it. Alternatively, you can follow [these instructions](../installation-and-operation/running-metabase-on-docker#pro-or-enterprise-quick-start). The code featured in this guide can be found in our [sample repo](https://github.com/metabase/metabase-nodejs-express-interactive-embedding-sample). ## Set up SSO and full app embedding in Metabase ### Have a dashboard ready to embed You'll first need a dashboard to embed. If you don't have one yet, you can use the Example Dashboard Metabase includes on new instances or you can generate one using x-rays. Visit that dashboard and make a note of its URL, e.g. `/dashboard/1-e-commerce-insights`. You'll need to put this relative URL in your app, as you'll use the dashboard as the first page that logged-in people will see when they visit the analytics section in your app. It's enough to include the ID only and omit the rest of the URL, e.g. `/dashboard/1`. You could also use the dashboard's [Entity ID](../installation-and-operation/serialization#metabase-uses-entity-ids-to-identify-and-reference-metabase-items). On the dashboard, click on the **info** button. On the **Overview** tab, look for the dashboard's **Entity ID**. Copy that Entity ID. You'll use that Entity ID in the iframe's `src` URL: (e.g., `src=/dashboard/entity/[Entity ID]`). ### Enable full app embedding In Metabase, click the **grid** icon in the upper right and go to **Admin > Embedding** and toggle on **Enable full app embedding**. Under **Authorized origins**, add the URL of the website or web app where you want to embed Metabase. If you're running your app locally, you can add localhost and specify the port number, e.g. `http://localhost:8080`. #### SameSite configuration If you're embedding Metabase in a different domain, you may need to [set the session cookie's SameSite value to `none`](./full-app-embedding#embedding-metabase-in-a-different-domain). ### Set up SSO with JWT in your Metabase #### Enable authentication with JWT While still in the **Embedding settings** section, scroll down and click on **Authentication** under **Related settings**. On the card that says **JWT**, click the **Setup** button (you may have to scroll down to view the JWT card). ![Admin settings: Authentication > JWT setup.](./images/jwt-setup.png) #### Set JWT Identity provider URI In your app, you'll create a route for SSO at `/sso/metabase`. In the **JWT IDENTITY PROVIDER URI** field, enter the URL of your SSO route. For example, our sample app runs on port 8080, so in that case this JWT IDENTITY PROVIDER URI could be `http://localhost:8080/sso/metabase`. #### Generate a JWT signing key Click on the **Generate key** button to generate a signing key. Keep this key a secret. You'll use it on your server. If you generate another key, you'll overwrite the existing key, so you'll need to update the key in your app as well. Copy this key, as you'll need it in the next section. ### Save and enable JWT authentication We'll set up group synchronization later, but for now, be sure to click the **Save and enable** button to activate JWT authentication. ## Set up SSO with JWT in your app's server ### Add the signing key and Metabase site URL to your app Here you'll need to input some values for your SSO to work. You'll want to declare up two constants in your app: - `METABASE_JWT_SHARED_SECRET`, paste the JWT signing key that you got from your Metabase here. - `METABASE_SITE_URL`, which points to your Metabase instance's root path. ```javascript const METABASE_JWT_SHARED_SECRET = "YOURSIGNINGKEY"; const METABASE_SITE_URL = "https://your-domain.metabaseapp.com"; ``` The signing key should preferably be setup as an environment variable, to avoid accidentally committing your key to your app's repo. ### Add a JWT library to your app's server Add a JWT library to your app. For example, if you're using a Node backend with JavaScript, we recommend using [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken). In your terminal: ```sh npm install jsonwebtoken --save ``` And in your app, require the library: ```javascript ``` ### Restricting access to certain routes Presumably, your app already has some way of making sure some routes are only accessible after having signed in. Our examples use a simple helper function named `restrict` that protects these routes: ```javascript ``` ### Add a function to sign users We need to write a function to sign user JWTs, using the JWT library. ```javascript ``` ### Add a `sso/metabase` route You'll need to add a route to sign people in to your Metabase via SSO using JWT. If the person isn't signed in to your app yet, your app should redirect them through your sign-in flow. In the code below, this check and redirection is handled by the `restrict` function we introduced earlier. ```javascript ``` Metabase creates an account for first-time sign-ins. ### CHECKPOINT: sign in to your Metabase using SSO Make sure you are signed out of your Metabase. From the Metabase sign-in page, click on "Sign in with SSO". You should be redirected to your app. Log in to your app. Your app should redirect you to your Metabase welcome page. If the person doesn't yet have a Metabase account, Metabase should create an account for them. ## Embed Metabase in your app Now to embed your Metabase in your app. You'll want to set up a route to serve your embedded analytics. Let's call it `/analytics`. Note that we're using the `restrict` helper function (defined above) because this page should only be viewable after people sign in to your app. In this route, we need to render an iframe that will load your Metabase. The `src` attribute of the iframe should point to the relative path of the SSO endpoint of your app. Once the person signs in to your app (and therefore in to your Metabase), we add the query string parameter `return_to` so that the iframe displays the requested dashboard. `METABASE_DASHBOARD_PATH` should be pointing to the relative path of the dashboard you created at the beginning of this guide (`/dashboard/[ID]`, or if you used the dashboard's Entity ID: `/dashboard/entity/[Entity ID]`). ```javascript ``` The `METABASE_DASHBOARD_PATH` is just the first thing people will see when they log in, but you could set that path to any Metabase URL. And since you're embedding the full Metabase, people will be able to drill through the data and view other questions, dashboards, and collections. ### CHECKPOINT: view a Metabase dashboard in your app People using your app should now be able to access `/analytics` and view your embedded Metabase dashboard. How to test: Sign in to your app and visit the `/analytics` route. You should see the Metabase dashboard. > If you're using the Safari browser, and you're serving Metabase and your app from different domains, you may need to go to Safari's settings and turn off [Prevent cross-site tracking](https://support.apple.com/guide/safari/prevent-cross-site-tracking-sfri40732/mac). ## Set up a group in Metabase Now that you have SSO and full app embedding set up, it's time to set up groups so that you can apply permissions to your embedded Metabase entities (questions, dashboards, collections, and so on). ### Add a `groups` key to your token Recall the `signUserToken` function used to create the JWTs. Add a `groups` key to the signed token that maps to an array. Metabase will look at the values in that array to see if any of the values map to a group in Metabase (We'll walk through mapping groups in a bit). ```javascript ``` ### Create a group in Metabase In Metabase, click the **grid** icon and go to **Admin** > **People** > **Groups**. Click the **Create a group** button. Add a group that corresponds with a group in your app. If you're using the sample app, add a group called `Customer Acme`. ### Synchronize groups between Metabase and your app You'll map this string in the `groups` key to a Metabase group, so that when the person signs in via SSO, Metabase automatically assigns them to the appropriate Metabase group. In Metabase's admin section, go to **Authentication > JWT** and click **Edit**. In the **Group schema** section, toggle on **Synchronize group memberships**. If the names of groups in the `groups` array match Metabase group names exactly (e.g. both are `"Customer Acme"`), then the groups will be mapped automatically. If the JWT group names and Metabase group names don't match, then for each group you want to sync, add a group mapping. When you click **New mapping**, enter "Customer-Acme", the string that you included in the `groups` array in your JWT payload. You can then associate that group name with the Metabase group "Customer Acme" that we created earlier. ![Mapping user attributes to groups.](./images/sync-groups.png) Be sure to **Save changes**. ### CHECKPOINT: verify that Metabase assigns people to groups when they log in First, sign out of Metabase and sign in using SSO. Then sign out and sign in to your Metabase as an admin and go to **Admin** > **People** section and verify that Metabase added the person to the appropriate group. Note: only Metabase admins and group managers are aware of groups. Basic users have no concept of groups, and no way of knowing which groups they're a part of. ## Set permissions Now to apply permissions to that group so that people only see data specific to their accounts. ### Reset permissions for the All Users group Metabase ships with two initial groups: "Admins" and "All Users". By default, Metabase gives the "All Users" group access to connected data sources. And since Metabase grants people the privileges of their most permissive group, you'll want to restrict what the "All Users" groups can see before you add them to groups with limited or no access to data sources and collections. To reset permissions for the All users group, click the **grid** icon and go to **Admin** > **Permissions**. Under the **Data** tab, go to **Groups** and select **All Users**. For the **Sample Database** in the **View data** column, select "Blocked". Click **Save changes** and a modal will pop up summarizing what you're changing. Click **Yes**. ![Resetting permissions of the All Users group to](./images/all-users.png) ### Allow view access to the automatically generated dashboards collection Still in the **Permissions** tab, click on the **Collections** sub-tab, then on the **Automatically generated dashboards** collection, and set the **Collection access** permissions for the **All Users** group to **View**. Click **Save changes**, then **Yes**. ### Add a user attribute to the token You can include user attributes in the JSON web token. Metabase will pick up any keys from the JWT payload and store them as user attributes. Among other use cases, you can use these user attributes to set row-level permissions on tables, so people can only see results tied to their accounts. If you're using our sample app, edit the `signUserToken` function used to create the JWT by adding a key `account_id` with value `28`. ```javascript ``` That user ID will correspond to the `Account ID` column in the Sample Database's Invoices table. We'll use this `account_id` user attribute for row and column security on the Invoices table, so people will only see rows in that table that contain their account ID. Note that to persist the user attribute in Metabase, you'll need to log in. Log in to your app as a non-admin, and visit the page with your embedded Metabase. ### Set row-level permissions In Metabase, go to **Admin** > **Permissions**. Under the **Data** tab on the left, click on a group. For "Sample Database", change its **Data access** column to **Granular**. Metabase will display a list of the tables in the database. Next, change **View data access** for the "Invoices" table to **Row and column security**. ![Adding row and column security to a table.](./images/secured-invoices-table.png) Next, Metabase will prompt you with a modal to associate a column in that table with a user attribute. Leave the **Filter by a column in a table** option checked, and associate the "Account ID" column in the Invoices table with the user attribute `account_id`. (Note that Metabase will only display the user attributes if the user has signed in through SSO before.) Click **Save** to confirm your select. Then click the **Save changes** button in the upper right. Metabase will ask if you're sure you want to do this. You _are_ sure. ### CHECKPOINT: view secured dashboard Make sure you've logged out of your previous session. Log in to your app, navigate to `/analytics`. The dashboard will now present different information, since only a subset of the data is visible to this person. Click on **Browse Data** at the bottom of the left nav. View your secured **Invoices** table, and you should only see rows in that table that are associated with the person's account. ## Hiding Metabase elements You can decide to [show or hide various Metabase elements](./full-app-embedding#showing-or-hiding-metabase-ui-components), like whether to show the nav bar, search or the **+New** button, and so on. For example, to hide the logo and the top navigation bar of your embedded Metabase, you'd append the query string parameters `?logo=false&top_nav=false` to the `return_to` URL that you include in the SSO redirect. In the handler of your `/sso/metabase` path, add the query parameters: ```javascript ``` ### CHECKPOINT: verify hidden UI elements Sign out and sign in to your app again and navigate to `/analytics`. Your embedded Metabase should not include the logo or the top navigation. ## Next steps You can [customize how Metabase looks](../configuring-metabase/appearance) in your app: fonts, colors, and logos. --- # Full app embedding **Full app embedding** lets you embed the entire Metabase app in an iframe. Full app embedding integrates your [permissions](../permissions/introduction) and [SSO](../people-and-groups/start#authentication) to give people the right level of access to [query](../questions/query-builder/editor) and [drill-down](/learn/metabase-basics/querying-and-dashboards/questions/drill-through) into your data. > If you are just starting out with Metabase embedding, consider using [Modular embedding](./modular-embedding) instead of full app embedding - it's an improved, more customizable option for embedding individual Metabase components. ## Full app embedding demo To get a feel for what you can do with full app embedding, check out our [Full app embedding demo](/embedding-demo). To see the query builder in action, click on **Reports** > **+ New** > **Question**. ## Quick start Check out the [Full app embedding quick start](./full-app-embedding-quick-start-guide). ## Prerequisites for full app embedding 1. Make sure you have a [license token](../installation-and-operation/activating-the-enterprise-edition) for a [Pro or Enterprise plan](https://store.metabase.com/checkout/login-details). 2. Organize people into Metabase [groups](../people-and-groups/start). 3. Set up [permissions](../permissions/introduction) for each group. 4. Set up [SSO](../people-and-groups/start#authentication) to automatically apply permissions and show people the right data upon sign-in. In general, **we recommend using [SSO with JWT](../people-and-groups/authenticating-with-jwt)**. If you're dealing with a [multi-tenant](/learn/metabase-basics/embedding/multi-tenant-self-service-analytics) situation, check out our recommendations for [Configuring permissions for different customer schemas](../permissions/embedding). If you have your app running locally, and you're using the Pro Cloud version, or hosting Metabase and your app in different domains, you'll need to set your Metabase environment's session cookie SameSite option to "none". ## Enabling full app embedding in Metabase 1. Go to **Admin > Embedding**. 2. Click **Enable Full app embedding**. 3. Under **Authorized origins**, add the URL of the website or web app where you want to embed Metabase (such as `https://*.example.com`). ## Setting up embedding on your website 1. Create an iframe with a `src` attribute set to: - the [URL](#pointing-an-iframe-to-a-metabase-url) of the Metabase page you want to embed, or - an [authentication endpoint](#pointing-an-iframe-to-an-authentication-endpoint) that redirects to your Metabase URL. 2. Optional: Depending on the way your web app is set up, set [environment variables](../configuring-metabase/environment-variables) to: - [Add your license token](../configuring-metabase/environment-variables#mb_premium_embedding_token). - [Embed Metabase in a different domain](#embedding-metabase-in-a-different-domain). - [Secure your embed](#securing-full-app-embeds). 3. Optional: Enable communication to and from the embedded Metabase using supported [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) messages: - [From Metabase](#supported-postmessage-messages-from-embedded-metabase) - [To Metabase](#supported-postmessage-messages-to-embedded-metabase) 4. Optional: Set parameters to [show or hide Metabase UI components](#showing-or-hiding-metabase-ui-components). Once you're ready to roll out your full app embed, make sure that people **allow** browser cookies from Metabase, otherwise they won't be able to log in. ### Pointing an iframe to a Metabase URL Go to your Metabase and find the page that you want to embed. For example, to embed your Metabase home page, set the `src` attribute to your [site URL](../configuring-metabase/settings#site-url), such as: ``` src="https://metabase.yourcompany.com/" ``` To embed a specific Metabase dashboard, you'll want to use the dashboard's Entity ID URL `/dashboard/entity/[Entity ID]`. ``` src="https://metabase.yourcompany.com/dashboard/entity/[Entity ID]" ``` To get a dashboard's Entity ID, visit the dashboard and click on the **info** button. In the **Overview** tab, copy the **Entity ID**. Then in your iframe's `src` attribute to: ``` src=https://metabase.yourcompany.com/dashboard/entity/Dc_7X8N7zf4iDK9Ps1M3b ``` If your dashboard has more than one tab, select the tab you want people to land on and copy the Tab's ID. Add the tab's ID to the URL: ``` src=https://metabase.yourcompany.com/dashboard/entity/Dc_7X8N7zf4iDK9Ps1M3b?tab=YLNdEYtzuSMA0lqO7u3FD ``` You _can_ use a dashboard's sequential ID, but you should prefer the Entity ID, as Entity IDs are stable across different Metabase environments (e.g., if you're testing on a staging environment, the Entity IDs will remain the same when [exporting the data and importing it](../installation-and-operation/serialization) into a production environment). If you want to point to a question, collection, or model, visit the item, click on its info, grab the item's Entity ID and follow the url structure: `/[Item type]/entity/[Entity-Id]`. Examples: - `/collection/entity/[Entity ID]` - `/model/entity/[Entity ID]` - `/question/entity/[Entity ID]` ### Pointing an iframe to an authentication endpoint Use this option if you want to send people directly to your SSO login screen (i.e., skip over the Metabase login screen with an SSO button), and redirect to Metabase automatically upon authentication. You'll need to set the `src` attribute to your auth endpoint, with a `return_to` parameter pointing to the encoded Metabase URL. For example, to send people to your SSO login page and automatically redirect them to `https://metabase.yourcompany.com/dashboard/1`: ``` https://metabase.example.com/auth/sso?return_to=http%3A%2F%2Fmetabase.yourcompany.com%2Fdashboard%2F1 ``` If you're using [JWT](../people-and-groups/authenticating-with-jwt), you can use the relative path for the redirect (i.e., your Metabase URL without the [site URL](../configuring-metabase/settings#site-url)). For example, to send people to a Metabase page at `/dashboard/1`: ``` https://metabase.example.com/auth/sso?jwt=&return_to=%2Fdashboard%2F1 ``` You must URL encode (or double encode, depending on your web setup) all of the parameters in your redirect link, including parameters for filters (e.g., `filter=value`) and [UI settings](#showing-or-hiding-metabase-ui-components) (e.g., `top_nav=true`). For example, if you added two filter parameters to the JWT example shown above, your `src` link would become: ``` https://metabase.example.com/auth/sso?jwt=&redirect=%2Fdashboard%2F1%3Ffilter1%3Dvalue%26filter2%3Dvalue ``` ## Cross-browser compatibility To make sure that your embedded Metabase works in all browsers, put Metabase and the embedding app in the same top-level domain (TLD). The TLD is indicated by the last part of a web address, like `.com` or `.org`. Note that your full app embed must be compatible with Safari to run on _any_ browser in iOS (such as Chrome on iOS). ## Embedding Metabase in a different domain > Skip this section if your Metabase and embedding app are already in the same top-level domain (TLD). If you want to embed Metabase in another domain (say, if Metabase is hosted at `metabase.yourcompany.com`, but you want to embed Metabase at `yourcompany.github.io`), you can tell Metabase to set the session cookie's SameSite value to "none". You can set session cookie's SameSite value in **Admin** > **Embedding** > **Security** > **SameSite cookie setting**. SameSite values include: - **Lax** (default): Allows Metabase session cookies to be shared on the same domain. Used for production instances on the same domain. - **None (requires HTTPS)**: Use "None" when your app and Metabase are hosted on different domains. Incompatible with Safari and iOS-based browsers. - **Strict** (not recommended): Does not allow Metabase session cookies to be shared with embedded instances. Use this if you do not want to enable session sharing with embedding. You can also set the [`MB_SESSION_COOKIE_SAMESITE` environment variable](../configuring-metabase/environment-variables#mb_session_cookie_samesite). If you're using Safari, you'll need to [allow cross-site tracking](https://support.apple.com/en-tj/guide/safari/sfri40732/mac). Depending on the browser, you may also run into issues when viewing embedded items in private/incognito tabs. Learn more about [SameSite cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite). ## Securing full app embeds Metabase uses HTTP cookies to authenticate people and keep them signed into your embedded Metabase, even when someone closes their browser session. If you enjoy diagrammed auth flows, check out [Full app embedding with SSO](./securing-embeds). To limit the amount of time that a person stays logged in, set [`MAX_SESSION_AGE`](../configuring-metabase/environment-variables#max_session_age) to a number in minutes. The default value is 20,160 (two weeks). For example, to keep people signed in for 24 hours at most: ```sh MAX_SESSION_AGE=1440 ``` To automatically clear a person's login cookies when they end a browser session: ```sh MB_SESSION_COOKIES=true ``` To manually log someone out of Metabase, load the following URL (for example, in a hidden iframe on the logout page of your application): ```sh https://metabase.yourcompany.com/auth/logout ``` If you're using [JWT](../people-and-groups/authenticating-with-jwt) for SSO, we recommend setting the `exp` (expiration time) property to a short duration (e.g., 1 minute). ## Supported postMessage messages _from_ embedded Metabase To keep up with changes to an embedded Metabase URL (for example, when a filter is applied), set up your app to listen for "location" messages from the embedded Metabase. If you want to use this message for deep-linking, note that "location" mirrors "window.location". ```json { "metabase": { "type": "location", "location": LOCATION_OBJECT_OR_URL } } ``` To make an embedded Metabase page (like a question) fill up the entire iframe in your app, set up your app to listen for a "frame" message with "normal" mode from Metabase: ```json { "metabase": { "type": "frame", "frame": { "mode": "normal" } } } ``` To specify the size of an iframe in your app so that it matches an embedded Metabase page (such as a dashboard), set up your app to listen for a "frame" message with "fit" mode from Metabase: ```json { "metabase": { "type": "frame", "frame": { "mode": "fit", "height": HEIGHT_IN_PIXELS } } } ``` ## Supported postMessage messages _to_ embedded Metabase To change an embedding URL, send a "location" message from your app to Metabase: ```json { "metabase": { "type": "location", "location": LOCATION_OBJECT_OR_URL } } ``` ## Group strategies for row and column security If you want multiple people from a single customer account to collaborate on questions and dashboards, you'll need to set up one [group](../people-and-groups/managing#groups) per customer account. You can handle [row and column security](../permissions/row-and-column-security) with a single, separate group. For example, each person could be part of a customer group that sets up data permissions with row and column security via a certain attribute that applies to everyone across all your customer accounts. Additionally, each person within a single customer account could also be a member of a group specific to that customer account. That way they can collaborate on collections with other people in their organization, without seeing stuff created by people from other customers' accounts. ## Showing or hiding Metabase UI components See [full app UI components](./full-app-ui-components). For more granular control over embedded components, consider using [Modular embedding](./modular-embedding) instead. ## Metabot in full-app embeds See [Embedded Metabot settings](../ai/settings#enable-metabot). ## Reference apps To build a sample full app embed using SSO with JWT, see our reference apps: - [Node.js + Express](https://github.com/metabase/metabase-nodejs-express-interactive-embedding-sample) (with [quick start guide](./full-app-embedding-quick-start-guide)) - [Node.js + React](https://github.com/metabase/sso-examples/tree/master/app-embed-example) ## Further reading - [Full app embedding quick start](./full-app-embedding-quick-start-guide) - [Strategies for delivering customer-facing analytics](/learn/metabase-basics/embedding/overview). - [Permissions strategies](/learn/metabase-basics/administration/permissions/strategy). - [Customizing Metabase's appearance](../configuring-metabase/appearance). --- # Full app embedding UI components To change the interface of your full app embed, you can add parameters to the end of your embedding URL. If you want to change the colors or fonts in your embed, see [Customizing appearance](../configuring-metabase/appearance). > For more granular control of embedded components, consider using [Modular embedding](./modular-embedding) instead of full app embedding - it's an improved, more customizable option for embedding Metabase elements. For example, you can disable Metabase's [top nav bar](#top_nav) and [side nav menu](#side_nav) like this: ``` your_embedding_url?top_nav=false&side_nav=false ``` ![Top nav and side nav disabled](./images/no-top-no-side.png) Here's an example using the URL constructor to add parameters to the URL for the iframe: ```tsx const mods = "logo=false&top_nav=true&search=true&new_button=true"; app.get("/sso/metabase", restrict, (req, res) => { const ssoUrl = new URL("/auth/sso", METABASE_SITE_URL); ssoUrl.searchParams.set("jwt", signUserToken(req.session.user)); ssoUrl.searchParams.set("return_to", `${req.query.return_to ?? "/"}?${mods}`); res.redirect(ssoUrl); }); ``` Parameters include: - [Action buttons](#action_buttons) - [Additional info](#additional_info) - [Breadcrumbs](#breadcrumbs) - [Entity types](#entity_types) - [Header](#header) - [Locale](#locale) - [Logo](#logo) - [New button](#new_button) - [Search](#search) - [Side nav](#side_nav) - [Top nav](#top_nav) > To make sure that query parameters are preserved when using [click behavior](../dashboards/interactive#customizing-click-behavior), configure the [Site URL](../configuring-metabase/settings#site-url) Admin setting to be your Metabase server URL. ## `action_buttons` Visible by default on question pages when the [header](#header) is enabled. To hide the action buttons such as **Filter**, **Summarize**, the query builder button, and so on: ``` header=false&action_buttons=false ``` ![Action buttons](./images/action-buttons.png) ## `additional_info` Visible by default on question and dashboard pages when the [header](#header) is enabled. To hide the gray text "Edited X days ago by FirstName LastName", as well as the breadcrumbs with collection, database, and table names: ``` header=false&additional_info=false ``` ![Additional info](./images/additional-info.png) ## `breadcrumbs` Shown by default in the top nav bar. Collection breadcrumbs show the path to the item (i.e., the collection(s) the item is in). This does not effect Data breadcrumbs if the user has Query Builder permissions. To hide the breadcrumbs: ``` breadcrumbs=false ``` ## `data_picker` `data_picker` controls the menu for selecting data sources in questions. ![Simple data picker](./images/data-picker.png) The default behavior for the data picker is: - Show tables and models. - Exclude metrics and questions. - Display a simple dropdown menu. If there are 100 or more items, Metabase will display a souped-up data picker. You can opt for the full data picker by setting `data_picker=staged`: ![Full data picker](./images/full-data-picker.png) The above data picker has three entity types selected: ``` data_picker=staged&entity_types=table,model,question ``` ## `entity_types` You can show or hide different entity types in the data picker, sidebar, and the New button menu. For example, you may only want to show tables: ``` entity_types=table ``` If only tables are allowed, the sidebar won't show models: ![Sidebar without models](./images/sidebar-without-models.png) Available entity types are: - `table` - `model` - `question` (only works with `data_picker=staged`) You can separate entity types with a comma: ``` entity_types=table,model ``` ## `header` Visible by default on question and dashboard pages. To hide a question or dashboard's title, [additional info](#additional_info), and [action buttons](#action_buttons): ``` header=false ``` ## `locale` You can change the language of the user interface via a parameter. For example, to set the locale to Spanish: ``` locale=es ``` Read more about [localization](../configuring-metabase/localization). ## `logo` Whether to show the logo that opens and closes the sidebar nav. Default is true. The logo's behavior depends on the `side_nav` setting: | `logo` | `side_nav` | Result | | ------ | ---------- | --------------------------------------------------------------------- | | true | true | Shows your configured logo in the sidebar | | true | false | No sidebar or logo functionality | | false | true | Shows a generic sidebar icon (gray when normal, brand color on hover) | | false | false | No sidebar or logo, with breadcrumbs aligned to the left edge | ## `new_button` Hidden by default. To show the **+ New** button used to create queries or dashboards: ``` top_nav=true&new_button=true ``` ## `search` Hidden by default. To show the search box in the top nav: ``` top_nav=true&search=true ``` ## `side_nav` The navigation sidebar is shown on `/collection` and home page routes, and hidden everywhere else by default. To allow people to minimize the sidebar: ``` top_nav=true&side_nav=true ``` ![Side nav](./images/side-nav.png) ## `top_nav` Shown by default. To hide the top navigation bar: ``` top_nav=false ``` ![Top nav bar](./images/top-nav.png) The `top_nav` parameter controls the visibility of the entire top navigation bar. When `top_nav` is set to `false`, all child elements (`search`, `new_button`, and `breadcrumbs`) are automatically hidden. When `top_nav` is set to `true`, you can individually control the visibility of these child elements. --- # Guest embeds Guest embeds are a way to embed basic Metabase components in your app without requiring you to create a Metabase account for each person viewing the charts and dashboards. But not logging people in to your Metabase has some major tradeoffs: see [limitations](#guest-embed-limitations). Even though you're not using SSO, guest embeds are still secure: Metabase will only load the embed if the request has a JWT signed with the secret shared between your app and your Metabase. The JWT also includes a reference to the resource to load (like the ID of the embedded item), and any values for parameters. To restrict data in guest embeds for specific people or groups, use [locked parameters](#locked-parameters). ## Turning on guest embedding in Metabase The path to embedding settings depends on your Metabase version: - **OSS**: **Admin > Embedding** - **Starter/Pro/Enterprise**: **Admin > Embedding > Guest embeds** Toggle **Enable guest embeds**. ## Creating a guest embed ![Sharing button to embed dashboard](./images/sharing-embed.png) To create a guest embed: 1. Go to the item that you want to embed in your website. You can also open a command palette with Ctrl/Cmd+K and type "New embed". 2. Click the **sharing icon**. 3. Select **Embed**. 4. Under **Authentication**, select **Guest**. 5. Optional: [customize the appearance of the embed](./appearance) 6. Optional: [Add parameters to the embed](./components). 7. Click **Publish**. 8. Get the code snippet that the wizard generates and add it to your app. ![Guest embed settings](./images/guest-embed-settings.png) ## Notes on the code the wizard generates You can edit the code (see [components](./components) and [appearance](./appearance)). But here's an overview of the code the wizard generates, and where to put it. ### Client-side code Add the embed script and configuration to your HTML: ```html ``` Then add the component for the item you want to embed: ```html ``` > Never hardcode JWT tokens directly in your HTML. Always fetch the token from your backend and pass the token to the web component programmatically. ### Server-side code Your server generates signed JWT tokens that authenticate the embed request. Here's an example using Node.js: ```javascript const jwt = require("jsonwebtoken"); const METABASE_SECRET_KEY = "YOUR_METABASE_SECRET_KEY"; const payload = { resource: { dashboard: 10 }, // or { question: 5 } for questions params: {}, exp: Math.round(Date.now() / 1000) + 10 * 60, // 10 minute expiration }; const token = jwt.sign(payload, METABASE_SECRET_KEY); ``` Replace `YOUR_METABASE_SECRET_KEY` with your [embedding secret key](#regenerating-the-embedding-secret-key). ### Component attributes You can set different attributes to enable/disable UI. Here are some example attributes: | Attribute | Description | | -------------------- | --------------------------------------------------------------------- | | `token` | Required. The signed JWT token from your server. | | `with-title` | Show or hide the title. Values: `"true"` or `"false"`. | | `with-downloads`\* | Enable or disable downloads. Values: `"true"` or `"false"`. | | `initial-parameters` | JSON string of parameter values. Example: `'{"category":["Gizmo"]}'`. | \* Disabling downloads is only available on [Pro](/product/pro) and [Enterprise](/product/enterprise) plans. Attributes will differ based on the type of thing you're embedding. Guest embeds have fewer options than embeds that use SSO. See more on [components and their attributes](./components). ### Customizing appearance of guest embeds Appearance settings available for guest embeds depend on your Metabase plan. If you're running Metabase OSS/Starter, you can select light or dark theme. If you're running Metabase Pro/Enterprise, you'll have access to granular customization options, see [Appearance](./appearance). ## Configuring parameters Parameters are disabled by default, which also makes them hidden from end-users. You can configure each parameter to be: - **Disabled**: Hidden from end-users. Can't be set. This is the default. - **[Editable](#editable-parameters)**: End-users can see and modify the parameter values. - **[Locked](#locked-parameters)**: Hidden from end-users, set by your server (not the end-users) via the JWT. To configure parameters: 1. Go to your embedded question or dashboard. 2. Click the **sharing icon** and select **Embed**. 3. Under **Parameters**, select the visibility option for each parameter, and optionally default value(s). 4. Click **Publish**. ### Editable parameters When you set Editable parameters, you can set default values for the filters, but users can change these when viewing the question or dashboard. **Server code** On the server, you pass an empty `params` object: ```javascript // you will need to install via 'npm install jsonwebtoken' or in your package.json const jwt = require("jsonwebtoken"); const METABASE_SECRET_KEY = "YOUR_METABASE_SECRET_KEY"; const payload = { resource: { dashboard: 10 }, params: {}, exp: Math.round(Date.now() / 1000) + 10 * 60, // 10 minute expiration }; const token = jwt.sign(payload, METABASE_SECRET_KEY); ``` **Client code** You set default parameters on the client side with the `initial-parameters` key. ```html ``` ### Locked parameters Locked parameters let you filter data without exposing the filter to the end-user. This is useful for restricting data based on who's viewing the embed (for example, showing each customer only their own data). To use locked parameters, you need to: 1. Set the parameter to **Locked** in the embed settings. 2. Include the parameter value in your JWT token on the server. 3. Publish the item. Here's an example of the server and client code that Metabase will generate if you lock a "category" parameter: **Server code (Node.js)** You set the locked parameter on the server, passing it in the token. ```javascript // Install via 'npm install jsonwebtoken' const jwt = require("jsonwebtoken"); const METABASE_SECRET_KEY = "YOUR_METABASE_SECRET_KEY"; const payload = { resource: { dashboard: 10 }, params: { category: ["Gadget"], // Set the locked parameter value to Gadget }, exp: Math.round(Date.now() / 1000) + 10 * 60, // 10 minute expiration }; const token = jwt.sign(payload, METABASE_SECRET_KEY); ``` **Client code (HTML)** The parameter is set by the JWT: ```html ``` The end user won't see the "category" filter, but the dashboard will only show data for the "Gadget" category (or whatever you passed to the "category" array in the `params` object in the server payload you used to sign the JWT). ## Disabling embedding for a question or dashboard 1. Visit the embeddable question or dashboard. 2. Click the **sharing icon** (square with an arrow pointing to the top right). 3. Select **Embed**. 4. Select **Guest embedding** 5. Click **Unpublish**. Admins can find a list of embedded items in **Admin > Embedding** (on Pro and Enterprise plans, check the **Guest embeds** tab). ## Removing the "Powered by Metabase" banner ![Powered by Metabase](./images/powered-by-metabase.png) The banner appears on guest embeds created with Metabase's open-source version. To remove the banner, you'll need to upgrade to a [Pro](/product/pro) or [Enterprise](/product/enterprise) plan. ## Regenerating the embedding secret key Your embedding secret key is used to sign JWTs for all of your embeds. 1. Go to **Admin > Embedding**. On Pro and Enterprise plans, check the **Guest embeds** tab. 2. Under **Regenerate secret key**, click **Regenerate key**. This key is shared across all guest embeds. Whoever has access to this key could get access to all embedded artifacts, so keep this key secure. If you regenerate this key, you'll need to update your server code with the new key. ## Custom destinations on dashboards in guest embeds You can only use the **URL** option for [custom destinations](../dashboards/interactive#custom-destinations) on dashboards with guest embedding. External URLs will open in a new tab or window. You can propagate filter values into the external URL, unless the filter is locked. ## Translating embeds To translate an embed, set the `locale` in `window.metabaseConfig`: ```html ``` The `locale` setting works for all modular embeds (guest, SSO, and SDK). Metabase will automatically translate UI elements (like menus and buttons). To also translate content like dashboard titles and filter labels, you'll need to upload a [translation dictionary](./translations). ## How guest embedding works Guest embeds use web components (`` and ``) that communicate with your Metabase instance. Each embed request requires a JWT token signed with your secret key. When a visitor views your page: 1. Your server generates a signed JWT token containing the resource ID (dashboard or question) and any locked parameters. 2. The web component sends the token to Metabase. 3. Metabase verifies the JWT signature using your secret key. 4. If valid, Metabase returns the embedded content. For interactive filters, you can pass initial parameter values via the `initial-parameters` attribute. When a visitor changes a filter, the web component handles the update automatically. The signed JWT is generated using your [Metabase secret key](#regenerating-the-embedding-secret-key). The secret key tells Metabase that the request can be trusted. Note that this secret key is shared for all guest embeds, so whoever has access to that key will have access to all embedded artifacts. If you want to embed charts with additional interactive features, like [drill-down](/learn/metabase-basics/querying-and-dashboards/questions/drill-through) and [self-service querying](../questions/query-builder/editor), see [Modular embedding](./modular-embedding). ## Using guest embeds with the SDK If you're using the [Modular Embedding SDK](./sdk/introduction), and you also want to embed a question or dashboard using guest authentication, you'll still need to visit the item in your Metabase and publish the item. You can ignore the code the wizard generates, but in order for Metabase to know it's okay to serve the item, you need to publish it. One limitation, however, is that you can only have one type of authentication per page of your app. For example, on a single page in your app, you can't have both one question using guest authentication and another question using SSO. ## Guest embed limitations Because guest embeds don't require you to create a Metabase account for each person via SSO, Metabase can't know who is viewing the embed, and therefore can't give them access to all their data and all the cool stuff Metabase can do. Guest embeds can't take advantage of: - [Row and column security](../permissions/row-and-column-security) - [Database routing](../permissions/database-routing) - [Drill-through](/learn/metabase-basics/querying-and-dashboards/questions/drill-through) - [Usage analytics](../usage-and-performance-tools/usage-analytics) - [Query builder](../questions/query-builder/editor) - [AI chat](./sdk/ai-chat) For those features, check out [Modular embedding with SSO](./modular-embedding). ## Further reading - [Reference apps repo](https://github.com/metabase/embedding-reference-apps). - [Strategies for delivering customer-facing analytics](/learn/metabase-basics/embedding/overview). - [Publishing data visualizations to the web](/learn/metabase-basics/embedding/charts-and-dashboards). - [Customizing Metabase's appearance](../configuring-metabase/appearance). --- # Embedding introduction You can embed Metabase tables, charts, and dashboards—even Metabase's query builder—in your website or application. Here are the different ways you can embed Metabase: - [Modular embedding](#modular-embedding) - [Full app embedding](#full-app-embedding) - [Public links](#public-links-and-embeds) ## Modular embedding With [modular embedding](./modular-embedding), you can embed individual Metabase components in your web app. You can use guest embeds for basic functionality, or use SSO to take full advantage of Metabase. You can use two different ways to authenticate modular embeds: - [SSO](#modular-embedding) - [Guest](#guest-embedding) Here's a basic breakdown of what each auth type enables: | Component | SSO | Guest | | ----------------------------------------------------- | --- | ----- | | Chart | ✅ | ✅ | | Chart with drill-through | ✅ | ❌ | | Dashboard | ✅ | ✅ | | Dashboard with drill-through | ✅ | ❌ | | [Query builder](../questions/query-builder/editor) | ✅ | ❌ | | Browser to navigate collections | ✅ | ❌ | | Metabot AI chat | ✅ | ❌ | Currently, you can't embed [documents](../documents/introduction). ### SSO embeds With SSO, Metabase can know who's viewing what, which unlocks a lot of power. You can automatically apply [data permissions](../permissions/embedding), which means you can give people access to all the cool tools Metabase provides, and everyone will only ever see the data they're allowed to. **When to use SSO**: You want to offer multi-tenant, self-service analytics, or you want to include the query builder, AI chat, drill-through, or a collection browser. ### Guest embedding [Guest embeds](./guest-embedding) are a secure way to embed charts and dashboards. Guest embedding works on all Metabase plans, including OSS and Starter. **When to use guest embeds**: simple embedding use cases where you don't want to offer ad-hoc querying or chart drill-through. To filter data relevant to the viewer, you can use guest embeds with [locked parameters](./guest-embedding#locked-parameters). ## Public links and embeds If you'd like to share your data with the good people of the internet, admins can create a [public link](./public-links) or embed a question or dashboard directly in your website. **When to use public links and embeds**: One-off charts and dashboards. Admins can use public links when you just need to show someone a chart or dashboard without giving people access to your Metabase. And you don't care who sees the data; you want to make the item available to everyone. ## Full app embedding [Full app embedding](./full-app-embedding) allows you to embed the entire Metabase app in an iframe, and integrate Metabase SSO with your app's authentication. ## Comparison of embedding types | Action | [Modular SDK](./sdk/introduction) | [Modular SSO](./modular-embedding) | [Modular Guest](./guest-embedding) | [Full app](./full-app-embedding) | [Public](../embedding/public-links) | | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ------------------------------------- | ------------------------------------- | ----------------------------------- | -------------------------------------- | | Charts and dashboards | ✅ | ✅ | ✅ | ✅ | ✅ | | [Filter widgets](/glossary/filter-widget) | ✅ | ✅ | ✅ | ✅ | ✅ | | Export results\* | ✅ | ✅ | ✅ | ✅ | ✅ | | [Locked filters](./static-embedding-parameters#restricting-data-in-a-static-embed-with-locked-parameters) | ❌ | ❌ | ✅ | ❌ | ❌ | | [Data segregation](../permissions/embedding) | ✅ | ✅ | ❌ | ✅ | ❌ | | [Drill-through menu](/learn/metabase-basics/querying-and-dashboards/questions/drill-through) | ✅ | ✅ | ❌ | ✅ | ❌ | | [Query builder](../questions/query-builder/editor) | ✅ | ✅ | ❌ | ✅ | ❌ | | [Basic appearance customization](../configuring-metabase/appearance)\*\* | ✅ | ✅ | ✅ | ✅ | ✅ | | [Advanced theming](./appearance) | ✅ | ✅ | ❌ | ❌ | ❌ | | [Usage analytics](../usage-and-performance-tools/usage-analytics) | ✅ | ✅ | ❌ | ✅ | ❌ | | Embed individual Metabase components | ✅ | ✅ | ❌ | ❌ | ❌ | | Manage access and interactivity per component | ✅ | ✅ | ❌ | ❌ | ❌ | | Custom layouts | ✅ | ❌ | ❌ | ❌ | ❌ | | Customize behavior with [plugins](./sdk/plugins) | ✅ | ❌ | ❌ | ❌ | ❌ | | AI chat | ✅ | ✅ | ❌ | ✅ | ❌ | \* Each embedding type allows data downloads by default, but only [Pro and Enterprise](/pricing/) plans can disable data downloads. \*\* Requires a [Pro and Enterprise](/pricing/) plan for any embedding type. ## Resources for AI agents If you're using an AI agent to help you embed Metabase in your app, check out [AI agent resources](./ai-agent-resources). ## Further reading - [Strategies for delivering customer-facing analytics](/learn/metabase-basics/embedding/overview). - [Publishing data visualizations to the web](/learn/metabase-basics/embedding/charts-and-dashboards). - [Multi-tenant self-service analytics](/learn/metabase-basics/embedding/multi-tenant-self-service-analytics). - [Customizing Metabase's appearance](../configuring-metabase/appearance). - [Securing embedded Metabase](./securing-embeds). --- # Modular embedding ![Modular embedding wizard](./images/modular-embedding-wizard.png) Modular embedding lets you embed and customize Metabase [components](./components) (like dashboards, the query builder, AI chat, and more) into your own application. You don't need to write embedding code on your own - just use the wizard to create a code snippet and paste it into your app. If you're using React, check out the [Modular embedding SDK](./sdk/introduction). > **Want guest embeds?** For modular embedding without authentication (available on all plans), see [Guest Embeds](./guest-embedding). ## Enable modular embedding 1. In Metabase, go to **Admin > Embedding**. 2. Toggle on **Enable modular embedding**. **For guest embeds**: You're done! Skip to [Create a new embed](#create-a-new-embed) or see the [Guest embedding guide](./guest-embedding) for detailed setup instructions. **For authenticated embeds** (Pro/Enterprise only), some more steps: 3. Under **Cross-Origin Resource Sharing (CORS)**, add the URLs of the websites where you want to embed Metabase (such as `https://*.example.com`). For testing embeds, you can use `localhost` which is always included in CORS policy. 4. If embedding on a different domain, see [Embedding Metabase in a different domain](./authentication#embedding-metabase-in-a-different-domain). ## Create a new embed You can generate a code snippet for embedding a component by going through the embed wizard. ### 1. Open the embed wizard In your Metabase: 1. Visit the item you want to embed. 2. Click the sharing icon. 3. Select **Embed**. ![Embed share button](./images/embed-share-button.png) You can also open a command palette with Ctrl/Cmd+K, type "New embed". You'll get a wizard to help you set up your embed. You can also go to **Admin > Embedding > Modular embedding** and click **New embed**. ![New modular embed from the admin settings](./images/new-modular-embed-from-admin-settings.png) ### 2. Pick how to authenticate the embed With SSO, Metabase can know who is viewing the embed, and it can unlock all of its bells and whistles (see this [comparison between SSO and guest embeds](./introduction#comparison-of-embedding-types)). This page covers the [SSO setup for your Metabase](./authentication). If you don't need to set up SSO, check out the [guest embed docs](./guest-embedding). ### 3. Customize your embed The exact customization options you see will depend on what type of entity you're embedding and which [Metabase plan](/pricing) you're on. You'll see a live preview of the embed with your customizations. ![Embed flow options for AI chat](./images/embed-flow-options.png) If you are on Metabase OSS/Starter plans, you'll be able to select light or dark themes for your components. On Metabase Pro/Enterprise, you'll be also be able to pick specific colors for brand, text, and background in the embed wizard, and add more customization options through [appearance settings](./appearance) later. All the customization options you select in this wizard will be reflected in code snippet that the embed wizard will generate for you, and you'll be able to add more options later. For example, this code defines the font, color, and size for text, background colors, and colors for filters and summaries: ```html ``` For more look-and-feel twiddling, see [appearance](./appearance). Once you're done customizing your embed, click **Next**. ## Add the embedding script to your app If you [create an embed through the built-in embed wizard](#create-a-new-embed), Metabase will generate a code snippet that you can copy and paste into your app. See the example below. You can later modify this code snippet to specify additional appearance options or change the behavior of some components. You'll add this code snippet to your app and refresh the page. The code snippets: - Loading the modular embedding library from your Metabase instance. - Set global configuration settings, like the URL of your Metabase and the `theme`. See [Page-level config](#page-level-config). - The component(s) to embed, with their parameters. See [Components](./components). Here's an example snippet: ```html ``` Note the `defer` attribute and the reference to your Metabase URL in the script that loads `embed.js` library. If you're embedding multiple components in a single page, you only need to include the ` ``` Due to iframe-resizer's licensing changes, we recommend that you use iframe-resizer version 4.3.2 or lower. ## Custom destinations on dashboards in static embeds You can only use the **URL** option for [custom destinations](../dashboards/interactive#custom-destinations) on dashboards with static embedding. External URLs will open in a new tab or window. You can propagate filter values into the external URL, unless the filter is locked. ## Translating static embeds See [Translating embedded questions and dashboards](./translations). ## Further reading - [Parameters for static embeds](./static-embedding-parameters). - [Reference apps repo](https://github.com/metabase/embedding-reference-apps). - [Strategies for delivering customer-facing analytics](/learn/metabase-basics/embedding/overview). - [Publishing data visualizations to the web](/learn/metabase-basics/embedding/charts-and-dashboards). - [Customizing Metabase's appearance](../configuring-metabase/appearance). --- # Tenants Tenants give you a way to group users and keep them isolated from users in other tenants. If you're building a SaaS app with embedded Metabase dashboards, you can assign the customers of your SaaS to tenants. The big advantage of grouping users into tenants is that you can use the same content and group permissions for all tenants, so you don't have to create all new stuff for each tenant, and each tenant only ever sees their own data. ![Tenants](./images/tenants.png) You can use tenants to: - Simplify bulk permissions through tenant groups and attributes. - Create collections of shared assets to avoid duplicating dashboards. - Provision tenants with SSO. To get started with tenants: 1. [Get familiar with tenant concepts](#concepts). 2. [Enable tenants](#enable-multi-tenant-strategy). 3. Create a [tenant](#create-new-tenants-in-metabase) and [tenant users](#create-tenant-users-in-metabase), or [provision them with SSO](#provisioning-and-assigning-tenants-with-jwt). 4. [Create shared collections](#create-shared-collections-for-tenants). 5. Set up [data](#data-permission-overview) and [collection](#collection-permissions-for-tenants) permissions. ## Concepts **Tenant** is an abstraction representing groups of users that share some properties but should be isolated from other users. For example, if you're building a SaaS app with embedded Metabase dashboards, the customers of your SaaS are tenants. While working with tenants in Metabase, you'll encounter different user and group types, and some special collection types. Let's establish some terminology. ### User types - **Tenant users** are the end users. In a B2B SaaS context, these are your customer's users. Usually tenant users interact with Metabase through an intermediate app (for example, through an embedded dashboard). - **Internal users** are "normal" Metabase users who don't belong to any tenant. Think Metabase admins, or devs building the dashboards that will be shared with tenants. ### Group types Tenant users and internal users can be organized into Metabase [groups](../people-and-groups/managing#groups). ![Tenant groups types](./images/tenant-groups.png) - **All tenant users** is a special group representing all individual end users across all tenants. This group can be used to [configure permissions](#data-permissions-for-tenants) for all tenant users. If you need more granular permission controls within each tenant, you can also create tenant groups. - **Tenant groups** can be used to create additional permission levels for tenant users. For example, if you're making recruiting software, every end user can be a recruiter (with access to all data about all open roles) or a hiring manager (with access to analytics for a specific role). So in Metabase, you can create two tenant groups - "Recruiters" and "Hiring managers" and configure appropriate permissions. Every tenant will be able to use tenant groups (so for example, every customer can have "Recruiters" and "Hiring managers"). - **All internal users** is a special group for all people who _aren't_ members of a tenant. These are the people working directly in Metabase. This group is equivalent to the "All users" group when multi-tenancy is not enabled. - **Internal groups** are additional groups for internal users. For example, you might have a special internal group "Analytics developers" who only have access to create dashboards that will be later shared with tenants, but don't have full admin access to your whole Metabase. Tenant users are completely isolated from internal users: tenant users can't be added to internal user groups, and internal users can't be added to tenant groups. ### Collection types Collections are like folders that can contain charts, dashboards, and models. They also serve as an organizational unit for permission management: if a certain group of people should have access to a certain set of assets, you should put those assets into a collection. ![Tenant collections](./images/tenant-collections.png) - **Shared collections** contain dashboards and charts that are shared between all tenants. For example, every tenant of a recruiting app should be able to see the number of job applications by date. You can create a [Metabase question](../questions/start) for "Count of applications by date" and save it into a shared collection. You'll need to [configure data permissions](#data-permissions-for-tenants) so that each tenant only sees _their_ job applications, and not _everyone's_ job applications (that would be bad). You can create many shared collections (or no shared collections at all, for that matter). For example, you can have a shared collection for analytics around recruitment and a separate shared collection for analytics around interviews. Shared collections can be optionally [synced to GitHub](#sync-shared-collections-to-github). - **Tenant collections** are collections specific to each tenant. They are created automatically for each tenant. If you have special customers with bespoke analytics that only this customer gets, you can put those bespoke dashboards and charts into the tenant's collection. Tenant collections can also serve as a place for the tenant users to create and save new questions that can be shared with their fellow tenant users (but not with users from other tenants). - **Internal collections**. Internal collections are exclusively an internal user concern. These are "normal" collections where you and other users internal to your Metabase can put the stuff that you don't want your end users to see (dashboards in development, internal metrics, maybe even analytics _about_ your tenants). Tenant users can't access any internal collections. - **Personal collection**. Every Metabase user, including tenant users, gets a personal collection - their own space to save new questions and dashboards (of course, if they otherwise have the permissions to build and save new questions). See [Collection permissions](#collection-permissions-for-tenants) for configuring access to different collection types. ## End user experience End users who are members of tenants will not know that they are members of tenants. In the experiences that expose users to Metabase collections (e.g. when using full-app embedding, modular embedding components with save enabled, or if tenant users are logging in directly into Metabase), tenant users see all the tenant and shared collections they have access to as just collections. ## Enable multi-tenant strategy _Admin settings > People_ You can create and manage your tenants exclusively through Metabase UI, or, if that's not your jam, [through SSO](#provisioning-and-assigning-tenants-with-jwt). Regardless of how you manage your tenants, you'll need to enable multi-tenant strategy in Metabase first. ![Edit tenant strategy](./images/edit-tenant-strategy.png) 1. Go to **Admin settings > People**. 2. Click on the **gear** icon above the list of people. 3. Choose **Multi-tenant strategy**. Changing Metabase to multi-tenant strategy enables special [user](#user-types) and [collection](#collection-types) types. You can create new tenants, tenant groups, and collections, and you get some additional admin settings in the People and Permissions tabs. If you have an existing permissions and collection setup that you'd like to translate to use tenants, see [Changing tenant strategy](#changing-tenant-strategy). Once you enable multi-tenant strategy, keep in mind that switching _from_ multi-tenant to single-tenant is a destructive action: all your tenant users and your tenant and shared collections will be disabled. See [Changing tenant strategy](#changing-tenant-strategy). ## Create new tenants in Metabase _Admin settings > People > Tenant_ ![New tenant user](./images/new-tenant.png) To create new tenants in Metabase: 1. [Enable multi-tenant strategy](#enable-multi-tenant-strategy), if you haven't yet. 2. Go to **Admin settings > People** . 3. Select **Tenants** on the left sidebar and click **New tenant**. 4. Fill out the information for the tenant: - **Tenant name**: Display name for the tenant that will be displayed to _internal_ users. Not exposed to external users. This name can be changed later. - **Tenant slug**: Unique identifier of the tenant. It can be used to [match JWT claims](#use-tenant-claim-to-sign-in-users) and for setting up [data permissions](#use-tenant-attributes-for-data-permissions). See [Tenant slug](#special-tenant-slug-attribute) for more information. - **Tenant attributes**: you can define tenant attributes that will be inherited by every tenant user, see [Tenant attributes](#tenant-attributes). You can avoid manually setting up tenants in Metabase by [provisioning tenants with JWT](#provisioning-and-assigning-tenants-with-jwt). ## Create tenant groups _Admin settings > People > Tenant groups_ Tenant groups are applicable across tenants. For example, you can have tenant groups "Basic users" and "Premium users", and every tenant will be able to use those groups. Groups can be used to configure permissions so that, among the tenant's users, some get Basic permissions while others get Premium. See [Tenant concepts](#concepts) for more information. To create a tenant group: 1. [Enable multi-tenant strategy](#enable-multi-tenant-strategy), if you haven't yet. 2. Go to **Admin settings > People**. 3. Select **Tenant groups** on the left sidebar and click **Create a group**. 4. Name your group. To add people to tenant groups, see [Add people to groups](../people-and-groups/managing#adding-people-to-groups). ## Create tenant users in Metabase _Admin settings > People > Tenant users_ Tenant users are the end users in tenants. In a B2B SaaS context, these are your customer's users. Usually tenant users interact with Metabase through an intermediate app (for example, through an embedded dashboard). To add tenant users in Metabase: 1. [Create a tenant](#create-new-tenants-in-metabase). 2. Go to **Admin settings > People** . 3. Select **Tenant users** on the left sidebar and click **New tenant user**. 4. Fill out the user information, including the tenant and tenant groups. If your tenant has [tenant attributes](#tenant-attributes), they'll be inherited by the user, but you can override the value in "Attributes". You can also [provision tenant users with JWT](#provisioning-and-assigning-tenants-with-jwt). ## Create shared collections for tenants ![Create shared collection](./images/create-shared-collection.png) Shared collections contain dashboards and charts that are shared between all tenants. If you're using shared collections, make sure that you configure [data permissions](#data-permissions-for-tenants) so that tenants can only see _their data_ in the shared collections. See [Tenant concepts](#concepts) for more information. To create a shared collection: 1. [Enable multi-tenant strategy](#enable-multi-tenant-strategy), if you haven't yet. 2. Open Metabase navigation sidebar by clicking on the **three lines** in the top left (that's regular Metabase, not Admin settings). 3. You should see "External collections" in the sidebar. If you don't, make sure you have enabled multi-tenant strategy. 4. Click on the **+** next to "External collections" to create a shared collection. You can have multiple shared collections and nested shared collections. You can also [sync shared collections to GitHub](#sync-shared-collections-to-github). ## Sync shared collections to GitHub _Admin settings > Remote Sync_ You can set up [Remote sync](../installation-and-operation/remote-sync) for shared collections. This means you'll be able to develop shared content in one Metabase, push it to a GitHub repo, and then have the shared content in your production Metabase always synced with that repo. See [Remote sync docs](../installation-and-operation/remote-sync) for more details. ## Tenant attributes _Admin settings > People > Tenants_ You can create tenant-level [user attributes](#tenant-attributes) which all users of the tenant inherit. This is useful for configuring attribute-based data permissions like [row-level security](../permissions/row-and-column-security), [impersonation](../permissions/impersonation), or [database routing](../permissions/database-routing). ![Edit tenant](./images/edit-tenant.png) There are three ways to add a tenant attribute, using the Metabase UI, the API, or JWT SSO. To create a tenant attribute manually using the Metabase UI: 1. Go to **Admin settings > People** 2. Select **Tenants** on the left sidebar. 3. Click on **three dots** next to the tenant. 4. Input the attribute key and value. For details on how to set tenant attributes using JWT tenant claims, see [Setting tenant attributes using tenant claims](#setting-tenant-attributes-using-tenant-claims) below. Once you add a tenant attribute, all users of that tenant will inherit the attribute, but the value can be overridden for any particular user, see [Edit user attributes](../people-and-groups/managing#adding-a-user-attribute). ### Special tenant slug attribute Each tenant user will get a system-defined attribute `@tenant.slug` that corresponds to the slug of the tenant. For example, if you created a tenant "Meowdern Solutions" with the slug `meowdern_solutions`, then every user from this tenant will get a special attribute `@tenant.slug : "meowdern_solutions"`. ![Slug attribute](./images/slug-attribute.png) If you create Metabase tenants through Metabase UI, you can choose the slug when creating the tenant. If you're [using JWT to provision tenants](#provisioning-and-assigning-tenants-with-jwt), tenant slug is the value of the `@tenant` claim for JWT (or another tenant assignment attribute you selected). Slug cannot be changed later. The special `@tenant.slug` attribute can be used just like a normal attribute to configure attribute-based permissions like [row-level security](../permissions/row-and-column-security), [impersonation](../permissions/impersonation), or [database routing](../permissions/database-routing). Your chosen tenant slug should correspond to how the tenant is actually identified in your setup. For example, if you want to use row-level security, and tenants are identified in your tables by their IDs (instead of names), then your tenant slug should be an ID as well. For example, if your data looks like this: ``` | Customer ID | Order number | Order date | Order total | | ----------- | ------------ | ---------- | ----------- | | 175924 | 3 | 2025-10-13 | 175.34 | | 680452 | 7 | 2025-10-13 | 34.56 | ``` and you want to enforce row-level security by `Customer ID`, then the tenant slug should have the form `175924` so that it could be matched to the Customer ID in your table. Similarly, if you want to use tenant slug for impersonation, you'll have to map the tenant slug to a database role, and for database routing - to a database. ## Provisioning and assigning tenants with JWT ### Use tenant claim to sign in users You can [set up JWT SSO](../people-and-groups/authenticating-with-jwt) and use the JWT to sign in tenant users. Once you [enable multi-tenant user strategy](#enable-multi-tenant-strategy), Metabase will look for a `@tenant` claim in your JWT to determine if the user is a tenant user, and which tenant the user belongs to. The value of `@tenant` key should be the tenant's slug. Here's an example of a JWT claim to sign in a tenant user: ```json { "email": "mittens@example.com", "first_name": "Mister", "last_name": "Mittens", "@tenant": "meowdern_solutions" } ``` If the user has already been assigned to a tenant (for example, through Metabase UI), then the JWT _must_ contain the tenant claim to sign the user in. ### Customize tenant claim By default, Metabase looks for a `@tenant` key in your JWT. To set up a different key: 1. Go to **Admin** > **Settings** > **Authentication** > **JWT** > **User attribute configuration** 2. Change the **Tenant assignment attribute** key to your preferred identifier. ### Provisioning tenants and users You can [turn on JWT user provisioning](../people-and-groups/authenticating-with-jwt) so that Metabase will automatically create users and tenants mentioned in the JWT. When user provisioning with JWT is enabled: 1. Metabase reads the tenant identifier from the JWT claim. By default, this is the `@tenant` key (you can configure this). 2. If the tenant doesn't exist, Metabase automatically creates it. Metabase will use the value of the `@tenant` key (or your chosen assignment attribute) as the tenant slug. 3. New users are automatically assigned to the tenant from their JWT. ### Setting tenant attributes using tenant claims To create tenant attributes from JWT SSO, include a claim `@tenant.attributes`: ```json { "@tenant": "meowdern_solutions", "@tenant.attributes": { "industry": "cat food" }, "email": "mittens@example.com", "first_name": "Mister", "last_name": "Mittens" } ``` If a tenant attribute with this name doesn't exist, Metabase will create the attribute and assign the value from the JWT claim. However, if the tenant attribute already exists, Metabase will **not** update the value. ### Troubleshooting JWT authentication with tenants Some common auth error messages and what they mean: - **Cannot add tenant claim to internal user**: JWT includes a tenant, but the user is an internal user. Only tenant users can have a tenant. - **Tenant claim required for external user**: JWT lacks a tenant claim, but the user is an external user. - **Tenant ID mismatch with existing user**: JWT has a different tenant than the user's assigned tenant. - **Tenant is not active**: The tenant exists but has been deactivated. ## Data permissions for tenants _Admin settings > Permissions_ Data permissions control what data people can see on charts and dashboards, and what they can do with that data. To control _which_ charts people see, you can use [collection permissions](#collection-permissions-for-tenants) instead. ### Data permission overview For an overview of how data permissions work in Metabase, see [Data permissions](../permissions/data). Here are the highlights (but please do read the full [Data permissions](../permissions/data) documentation): - **"View data"** controls which data each user group can see on dashboards and charts. For example, if your tenant data is commingled in one database, then you can use [**Row and column security**](../permissions/row-and-column-security) or [**Impersonation**](../permissions/impersonation) "View data" permissions to provide tenant users with access to only certain rows and columns. If every tenant has their data in a separate database, then instead of using permissions for data access control, you can use [**database routing**](../permissions/database-routing) to route queries to appropriate databases directly. - **Create queries** controls whether tenant users can create queries on the data they see. If you want to give your tenant users the ability to drill through (e.g., through `drills` parameter in [modular embedding](../embedding/modular-embedding)), you need to give them "Create queries" permissions, because a drill through is a new query. - **Download results** controls, unsurprisingly, whether people can download results of queries. You need to set download permissions if you want to give your users the option to download their data as a spreadsheet (for example, through `with-downloads` parameter in [modular embedding](../embedding/modular-embedding)). Data permissions in Metabase can be specified on database or table level and are granted to groups. You'll need to use the special **All tenant users**, and your tenant groups (if any) to assign data permissions. Keep in mind that Metabase permissions are additive, so if someone is a member of two different groups, they will be granted the _most_ permissive access. In particular, if "All tenant users" has "Can view" access to an entire table, but another tenant group has restricted access (e.g. "Row and column security"), then the users of the tenant group will still see all the data in the table because they get it via the "All tenant users" group. If you're using tenant groups, we recommend revoking access for "All tenant users" and configuring access on group-by-group basis. Please review [Data permissions documentation](../permissions/data) for more details on permissions setup. ### Use tenant attributes for data permissions [Row and column security](../permissions/row-and-column-security), [Impersonation](../permissions/impersonation), and Database routing require user attributes. You can [specify custom tenant attributes](#tenant-attributes) to configure data permissions based on attribute values. See [Tenant attributes](#tenant-attributes). Alternatively, you can use the special tenant slug attribute, see [Special slug attribute](#special-tenant-slug-attribute). ## Collection permissions for tenants Collection permissions control which entities (dashboards, questions, models etc) people can see. To configure what _data_ can people see in those entities, and what they can do with that data, see [Data permissions](#data-permissions-for-tenants) instead. In Metabase, there are different levels of collection permissions: **No** access, **View**-only access, and **Curate** access (allows for creating and saving new entities like dashboards). For more general information about collection permissions in Metabase, see [Collection permissions](../permissions/collections). Permissions are granted to groups. Which permissions are available to each group depend on the type of the group (external/tenant or internal) and the type of the collection. ### Tenant user collection permissions - For **internal collections**, tenant users will have **No** access. - For **shared collections**, tenant users can only have **View** or **No** access. This means that at _most_, tenant users can see existing entities but not create new ones. Different tenant groups can have different levels of access to different shared collections. For example, you can have a "Basic analytics" shared collection viewable by all users, and "Advanced analytics" collection only viewable by tenant group "Premium users". See [Configuring shared collection permissions](#configuring-shared-collections-permissions). - For **tenant collections**, tenant users will always have **Curate** permissions, which means that tenant users will always be able to save new questions in their tenant collection. If you don't want your tenant users to create and save their own charts, you'll need to disable "Create queries" [data permissions](#data-permissions-for-tenants) for tenant users, and, if you're embedding Metabase, configure the embedded UI components to disable saving. - For their own **personal collections**, tenant users will always have **Curate** permissions. ### Internal user collection permissions - Metabase Admins will have **Curate** access to all shared collections and all tenant collections. - Other internal groups and non-admin users have **No** access by default, but can be granted **View** or **Curate** access to **shared collections**. See [Configuring shared collection permissions](#configuring-shared-collections-permissions) for details. For configuring permissions to _internal_ collections for internal users, see [general docs on collection permissions](../permissions/collections). ### Configuring shared collections permissions _Admin settings > Permissions_ To configure access to shared collections for tenant and internal groups, go to **Admin settings > Permissions > Shared collections**. You can configure access for each shared collection and their subcollections for both internal and external users. See general docs on [collection permissions](../permissions/collections). Special **Root shared collection** controls who has access to _all_ shared collections. For example, if you want to make sure your internal users don't have access to any tenant shared collections, you can revoke permissions from the Root shared collection. When configuring permissions, remember that in Metabase, all permissions are additive, so if someone is a member of two different groups, they will be granted the _most_ permissive access. In particular, if "All tenant users" has "View" access to a shared collection, but another tenant group has "No" access to that collection specified in the permission settings, the users of the tenant group will still get "View" access because they have it via the "All tenant groups". If you're using tenant groups, we recommend revoking access for "All tenant users" and configuring access on group-by-group basis. ## Subscription permissions for tenants _Admin settings > Permissions_ By default, all tenant users will be created with **No** [subscription permissions](../permissions/application#subscriptions-and-alerts). If you want your users to be able to create subscriptions (either in full-app embedding, modular embedding, or by logging in directly to Metabase), you'll need to change the Subscription and alerts permissions to "Yes". ## Deactivate a tenant _Admin settings > People > Tenants_ **Deactivating a tenant will also deactivate all users who belong to this tenant**. To deactivate a tenant: 1. Go to **Admin settings > People**. 2. Select **Tenants** on the left sidebar. 3. Click on **three dots** next to the tenant. 4. Choose **Deactivate tenant**. All tenant users will be deactivated and won't be able to sign in anymore. Tenant users will not be permanently deleted (Metabase does not delete users, only deactivates), so even though the tenant users will be deactivated, you won't be able to create new users with the same email. ## Changing tenant strategy ### From single-tenant to multi-tenant When you enable multi-tenant strategy, all users that currently exist in Metabase will be considered "internal" users. If you don't want any of those users to become [tenant users](#concepts), you can just proceed with tenant setup as if you had a fresh new instance (create tenants, create collections, set up permissions, etc). However, if you want to assign some existing users to tenants, you'll need to: 1. Mark them as tenant users using the API call: ```sh PUT /api/user/:id {"tenant_id": 1} ``` 2. If you're using JWT for SSO, [add a `@tenant` claim to your JWT](#provisioning-and-assigning-tenants-with-jwt). 3. Set up [tenant groups](#concepts), [data permissions](#data-permissions-for-tenants), and [collection permissions](#collection-permissions-for-tenants) because you won't be able to use existing internal groups for tenant permissions. ### From multi-tenant to single-tenant If you disable multi-tenant strategy, _all your tenant users will be deactivated_ and _all collections will be deleted_ (although you'll get both users and collections back if you reactivate multi-tenant strategy later). So if you want to keep the active users but just disable tenancy features, you'll need some extra prep. 1. Replicate the tenant setup you have with regular groups and collections (instead of tenant groups and shared collections). Review documentation for Data permissions, Collection permissions, and User groups. Make sure to thoroughly test your setup with test users. A [development instance](../installation-and-operation/development-instance) might come in handy. 2. If you're using any tenant groups, remove the tenant group memberships of all tenant users. 3. Change the tenant users to internal users using API: ```sh PUT /api/user/:id {"tenant_id": null} ``` **If you don't do this step, all your users will be deactivated.** 4. Finally, disable the feature once everything is verified to work. ## Limitations - **Tenant collections and personal collections can't be disabled**. If you don't want your tenant users to create and save their own charts, you can disable "Create queries" [data permissions](#data-permissions-for-tenants), and, if you're embedding Metabase, configure the embedded UI components to disable saving. - **Tenant users can't change tenants.** Once an external user is assigned to a tenant, they cannot switch to another tenant. - **If you disable multi-tenant strategy, deactivated tenant users will not show up in "Deactivated users"**, but Metabase will still keep track of them and won't allow creating new users with the same email. - **There are no tenant-specific groups**. Tenant groups are shared between all tenants. If you need a special group for just some of your tenants, create a tenant group but don't add any members from the tenants that the group isn't applicable to. ## Further reading - [Embedding overview](./start) - [JWT authentication](../people-and-groups/authenticating-with-jwt) - [Permissions overview](../permissions/start) --- # Translate embedded components You can set a locale on modular embeds (guest, SSO, and SDK) to translate Metabase's UI. If you've uploaded a translation dictionary, Metabase will also translate content strings (like dashboard names and filter labels) for all [modular embeds](./modular-embedding). ## Set a locale to translate UI, and upload a dictionary to translate content To translate an embed's user interface, set the locale in the config. The `locale` setting works for all modular embeds (guest, SSO, and SDK). Metabase UI elements (like menus) will be translated automatically - you don't need to add translations for them to your dictionary. For guest and SSO embeds (not the SDK), set the `locale` in `window.metabaseConfig`: ```html ``` If you also want to translate content (like item titles, headings, filter labels, or data), you'll need to add a translation dictionary. ### SDK translations For the SDK, set the `locale` prop on the `MetabaseProvider` component: ```tsx ``` If you've uploaded a translation dictionary, the SDK will also translate content strings (like dashboard names and filter labels) to this locale from that dictionary. ## Add a translation dictionary The dictionary must be a CSV with these columns: - **Language** with the locale code - **String** with the string to be translated - **Translation** > Don't put any sensitive data in the dictionary, since anyone can see the dictionary—including viewers of public links. To add a translation dictionary: 1. Go to **Admin > Embedding**. 2. Under **Translate embedded dashboards and question**, click **Upload translation dictionary**. Uploading a new dictionary will replace the existing dictionary. To remove a translation dictionary, upload a blank dictionary. ## Example translation dictionary Metabase uses these dictionaries to translate user-generated content, like dashboard names in [modular embeds](./modular-embedding). | Language | String | Translation | | -------- | ----------- | ------------ | | pt-BR | Examples | Exemplos | | pt-BR | First tab | Primeira aba | | pt-BR | Another tab | Outra aba | | pt-BR | Title | Título | | pt-BR | Vendor | Vendedor | | IT | Examples | Esempi | Prefer hyphens in your `pt-BR` in your translation dictionary. Underscores are also acceptable (if you download the dictionary _after_ uploading it, Metabase will transform `pt-BR` to `pt_BR`), but since the `locale` parameter in modular embeds only accepts locales with hyphens (like `pt-BR`), we recommend using hyphens for consistency. [See a list of supported locales](../configuring-metabase/localization#supported-languages). ## Use full phrases in translation dictionaries Currently, Metabase doesn't tokenize strings for translations, so you should include exact phrases in your translation dictionary. For example, if you have a dashboard called "Monthly Sales", it's not sufficient to have translations of "Monthly" and "Sales" - you also need to include "Monthly Sales" as a full string. Exact translations also apply to strings that use punctuation and special characters. For example, if you have a question title "How many Widgets did we sell this week?", you must include that exact string (with "?") into the translation dictionary. Metabase would treat "How many Widgets did we sell this week" as a different string. Essentially, the strings are keys in a table Metabase looks up, so they must match exactly. ## Translations are case-insensitive Translations are case-insensitive. For example, if your translation dictionary has a translation: | Language | String | Translation | | -------- | -------- | ----------- | | pt-BR | Examples | Exemplos | `Examples`, `examples`, `EXAMPLES` would all be translated as `Exemplos`. That is, Metabase won't preserve the original case; it will output the exact translation string in the dictionary. ## Include markdown formatting in translation dictionaries If the strings you want to translate include markdown formatting, you'll need to include the formatting in the dictionary. For example: | Language | String | Translation | | -------- | -------------- | -------------- | | pt-BR | `**Examples**` | `**Exemplos**` | | pt-BR | `_Examples_` | `_Exemplos_` | | pt-BR | `## Examples` | `## Exemplos` | ## The AI chat component isn't translated One exception is that Metabase won't translate the text in the [AI chat component](./sdk/ai-chat). While Metabot can understand other languages, it works best in English. ## Further reading - [Modular embedding](./modular-embedding) - [Guest embedding](./guest-embedding) ---