> ## Documentation Index
> Fetch the complete documentation index at: https://basedash.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Embedding

> Embed dashboards or the full Basedash app into your product

Basedash supports two types of embedding:

* **Dashboard embedding**: Embed a dashboard as a read-only, interactive view in an iframe.
* **Full app embedding**: Embed the full Basedash app (dashboards + chat) inside your product with JWT SSO.

For a product overview and buyer-facing positioning, see Basedash [embedding](https://www.basedash.com/features/embedding) and the guide to [embedded analytics for SaaS](https://www.basedash.com/blog/embedded-analytics-for-saas-the-complete-guide-to-adding-dashboards-to-your-product).

## Dashboard embedding

Dashboard embedding is best when you want to show a specific dashboard inside another app.

Teams comparing embedded analytics vendors can also review the best [Explo alternatives](https://www.basedash.com/vs/explo-alternatives).

### Create an embed

1. Open the dashboard in Basedash
2. Click **Share** in the dashboard header
3. Enable the **Embedding** toggle
4. Copy the iframe snippet (or copy the plain URL)

Embeds use a share link with the `/shared/{id}` path.

```html theme={"dark"}
<iframe
  src="https://charts.basedash.com/shared/xyz789"
  width="100%"
  height="600"
  frameborder="0"
  allowfullscreen
  allow="clipboard-write"
></iframe>
```

### Dashboard filters

#### Visibility

Filters are hidden by default on embedded dashboards. To show a filter, edit the filter and enable the "Show on public dashboard" option. This is configured per-filter, allowing you to choose exactly which filters viewers can interact with. For more information on configuring filters, see the [filters and variables documentation](/features/filters-and-variables).

<Warning>
  When a filter is shown on a public dashboard, any viewer can provide arbitrary
  values. If the filter is used in a SQL query, viewers can inject any value
  into that query. Ensure this is acceptable for your use case, and always use
  read-only database credentials. For sensitive data, consider using [secure
  filtering](#secure-filtering) instead.
</Warning>

#### Secure filtering

Secure filtering allows you to lock filter values in public dashboard URLs using JWT tokens. This prevents viewers from modifying certain filter values, making it safe to share dashboards that filter data by tenant, user, or other sensitive parameters.

**When to use secure filtering**

When a filter is hidden (via "Show on public dashboard" disabled), the filter uses its default value and viewers cannot override it via URL parameters—those values are enforced server-side.

Use secure filtering when you need to **dynamically set different filter values for different viewers**:

* **Multi-tenant applications**: Lock a `company_id` or `tenant_id` filter so each customer only sees their own data
* **User-specific dashboards**: Lock a `user_id` filter to show personalized data
* **Sensitive data access**: Ensure viewers cannot modify filters that control access to restricted information

**How it works**

Instead of allowing viewers to select filter values, you generate a signed JWT containing the locked values and append it to the public dashboard URL. Basedash verifies the JWT signature and applies the locked values server-side, so viewers cannot tamper with them.

**URL format**

The secure filtering URL follows this pattern:

```
https://charts.basedash.com/shared/{publicSharingLinkId}/{jwtToken}
```

* **publicSharingLinkId**: The ID from your public share link. When you enable sharing on a dashboard and copy the link, it looks like `https://charts.basedash.com/shared/abc123`. The `abc123` part is your `publicSharingLinkId`.
* **jwtToken**: A signed JWT containing the locked filter values.

**JWT payload**

Your JWT must include these claims:

| Claim             | Type   | Required | Description                                             |
| ----------------- | ------ | -------- | ------------------------------------------------------- |
| `dashboardLinkId` | string | Yes      | Must match the `publicSharingLinkId` in the URL         |
| `params`          | object | Yes      | Key-value pairs of filter syntax names to locked values |
| `exp`             | number | Yes      | Expiration timestamp (Unix seconds)                     |
| `iat`             | number | No       | Issued-at timestamp (Unix seconds)                      |

The `params` object keys should match your filter's syntax name (e.g., `user_id`, `company_id`). Values can be strings, numbers, booleans, or arrays of strings.

**Example**

Here's an example JWT payload that locks a `company_id` filter:

```json theme={"dark"}
{
  "dashboardLinkId": "abc123",
  "params": {
    "company_id": "comp_456"
  },
  "exp": 1735689600,
  "iat": 1735686000
}
```

And example code to generate the token:

<CodeGroup>
  ```javascript Node.js theme={"dark"}
  import jwt from "jsonwebtoken";

  function generateSecureDashboardUrl(publicSharingLinkId, lockedParams) {
    const token = jwt.sign(
      {
        dashboardLinkId: publicSharingLinkId,
        params: lockedParams,
      },
      process.env.BASEDASH_EMBED_JWT_SECRET,
      { expiresIn: "1h" },
    );

    return `https://charts.basedash.com/shared/${publicSharingLinkId}/${token}`;
  }

  // Usage
  const url = generateSecureDashboardUrl("abc123", { company_id: "comp_456" });
  ```

  ```python Python theme={"dark"}
  import jwt
  import os
  from datetime import datetime, timedelta

  def generate_secure_dashboard_url(public_sharing_link_id, locked_params):
      payload = {
          'dashboardLinkId': public_sharing_link_id,
          'params': locked_params,
          'exp': datetime.utcnow() + timedelta(hours=1),
          'iat': datetime.utcnow(),
      }
      token = jwt.encode(
          payload,
          os.environ['BASEDASH_EMBED_JWT_SECRET'],
          algorithm='HS256'
      )
      return f'https://charts.basedash.com/shared/{public_sharing_link_id}/{token}'

  # Usage
  url = generate_secure_dashboard_url('abc123', {'company_id': 'comp_456'})
  ```
</CodeGroup>

**JWT secret**

The JWT secret used to sign tokens is the same secret used for [full app embedding](#step-1-enable-embedding). You can find it at **Settings → Security**.

<Warning>
  Store your JWT secret securely in environment variables. Never expose it in
  client-side code.
</Warning>

**Behavior**

When a JWT is provided in the URL:

* Locked filters are applied server-side and cannot be changed by viewers
* Locked filters are hidden from the filter bar on the dashboard
* Other filters (not included in the JWT) remain interactive if configured to show on public dashboards
* If the JWT is invalid, expired, or the signature doesn't match, the dashboard returns an error

## Full app embedding

Full app embedding lets you embed the entire Basedash experience inside your product. Your users can view dashboards, build new charts, and chat with the AI assistant—all without leaving your application.

This is ideal for customer portals, partner dashboards, or any scenario where you want to give users a complete BI experience powered by their own data.

### How it works

Full app embedding uses JWT-based single sign-on (SSO) inside an iframe:

1. **Your server generates a JWT** containing the user's identity and your Basedash organization ID
2. **The iframe loads the SSO endpoint** (`/api/sso/jwt?jwt=...`) with the token
3. **Basedash validates the JWT** against your organization's secret
4. **A session is created** for the user (creating their account if needed)
5. **The user is redirected** to your organization's home page inside the iframe

```mermaid theme={"dark"}
sequenceDiagram
    participant Frontend as Your Frontend
    participant Backend as Your Backend
    participant Basedash

    Frontend->>Backend: Request embed page
    Backend->>Frontend: Generate JWT for user
    Frontend->>Basedash: Load iframe: /api/sso/jwt?jwt=XXX
    Basedash->>Basedash: Verify JWT, create session
    Basedash->>Frontend: Redirect to org home (in iframe)
```

### Setup overview

Setting up full app embedding involves these steps:

1. Enable embedding and get your JWT secret
2. Generate JWTs server-side for your users
3. Load the iframe with the SSO URL

### Step 1: Enable embedding

Enable embedding for your organization by going to **Settings → Embedding → Enable full app embedding**.

You'll also need your JWT secret to sign tokens that authenticate users into the embedded app. You can find this at **Settings → Security**.

<Warning>
  Store your `jwtSecret` securely in your environment variables. Never expose it
  in client-side code.
</Warning>

You can also configure allowed origins on this page to restrict which domains can embed your organization.

### Step 2: Generate JWTs server-side

Your backend must generate a signed JWT for each user who accesses the embed. Here are examples in common languages:

<CodeGroup>
  ```javascript Node.js theme={"dark"}
  import jwt from "jsonwebtoken";

  function generateBasedashToken(user) {
    return jwt.sign(
      {
        email: user.email,
        orgId: process.env.BASEDASH_ORG_ID,
        firstName: user.firstName,
        lastName: user.lastName,
        role: "MEMBER", // or 'ADMIN'
      },
      process.env.BASEDASH_EMBED_JWT_SECRET,
      { expiresIn: "10m" },
    );
  }

  // Express route example
  app.get("/embed/basedash", (req, res) => {
    const token = generateBasedashToken(req.user);
    const embedUrl = `https://charts.basedash.com/api/sso/jwt?jwt=${token}`;

    res.render("embed", { embedUrl });
  });
  ```

  ```python Python theme={"dark"}
  import jwt
  import os
  from datetime import datetime, timedelta

  def generate_basedash_token(user):
      payload = {
          'email': user.email,
          'orgId': os.environ['BASEDASH_ORG_ID'],
          'firstName': user.first_name,
          'lastName': user.last_name,
          'role': 'MEMBER',
          'exp': datetime.utcnow() + timedelta(minutes=10),
          'iat': datetime.utcnow(),
      }
      return jwt.encode(
          payload,
          os.environ['BASEDASH_EMBED_JWT_SECRET'],
          algorithm='HS256'
      )

  # Flask route example
  @app.route('/embed/basedash')
  def basedash_embed():
      token = generate_basedash_token(current_user)
      embed_url = f'https://charts.basedash.com/api/sso/jwt?jwt={token}'
      return render_template('embed.html', embed_url=embed_url)
  ```

  ```ruby Ruby theme={"dark"}
  require 'jwt'

  def generate_basedash_token(user)
    payload = {
      email: user.email,
      orgId: ENV['BASEDASH_ORG_ID'],
      firstName: user.first_name,
      lastName: user.last_name,
      role: 'MEMBER',
      exp: 10.minutes.from_now.to_i,
      iat: Time.now.to_i
    }
    JWT.encode(payload, ENV['BASEDASH_EMBED_JWT_SECRET'], 'HS256')
  end

  # Rails controller example
  def basedash_embed
    token = generate_basedash_token(current_user)
    @embed_url = "https://charts.basedash.com/api/sso/jwt?jwt=#{token}"
  end
  ```

  ```go Go theme={"dark"}
  import (
      "os"
      "time"
      "github.com/golang-jwt/jwt/v5"
  )

  func generateBasedashToken(user User) (string, error) {
      claims := jwt.MapClaims{
          "email":     user.Email,
          "orgId":     os.Getenv("BASEDASH_ORG_ID"),
          "firstName": user.FirstName,
          "lastName":  user.LastName,
          "role":      "MEMBER",
          "exp":       time.Now().Add(10 * time.Minute).Unix(),
          "iat":       time.Now().Unix(),
      }

      token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
      return token.SignedString([]byte(os.Getenv("BASEDASH_EMBED_JWT_SECRET")))
  }
  ```
</CodeGroup>

### Step 3: Load the iframe

In your frontend, render an iframe pointing to the SSO URL:

```html theme={"dark"}
<iframe
  src="https://charts.basedash.com/api/sso/jwt?jwt=YOUR_JWT_TOKEN"
  width="100%"
  height="800"
  frameborder="0"
  allow="clipboard-write"
></iframe>
```

### Customization options

You can customize the appearance of the embedded app by appending query parameters to the SSO URL:

```html theme={"dark"}
<iframe
  src="https://charts.basedash.com/api/sso/jwt?jwt=YOUR_JWT_TOKEN&theme=dark&hide_org_name=true&hide_chat=true"
  width="100%"
  height="800"
  frameborder="0"
  allow="clipboard-write"
></iframe>
```

| Parameter                | Values                  | Default | Description                                                                 |
| ------------------------ | ----------------------- | ------- | --------------------------------------------------------------------------- |
| `theme`                  | `light`, `dark`, `auto` | `auto`  | Override the theme for the embedded app. `auto` uses the system preference. |
| `hide_org_name`          | `true`, `false`         | `false` | Show or hide the organization name in the embed sidebar                     |
| `hide_chat`              | `true`, `false`         | `false` | Show or hide the chat page and chat panel                                   |
| `hide_dashboards`        | `true`, `false`         | `false` | Show or hide dashboards in the embed sidebar                                |
| `hide_insights`          | `true`, `false`         | `false` | Show or hide the insights page in the embed sidebar                         |
| `hide_automations`       | `true`, `false`         | `false` | Show or hide the automations page in the embed sidebar                      |
| `hide_suggested_prompts` | `true`, `false`         | `false` | Show or hide the suggested prompts in the AI chat input                     |

At least one feature must remain visible. When the default chat page is hidden, the embedded app opens the first visible page in sidebar order: dashboards, insights, then automations.

### JWT claims reference

Your JWT must include these claims:

| Claim       | Type   | Required | Description                                |
| ----------- | ------ | -------- | ------------------------------------------ |
| `email`     | string | Yes      | The user's email address                   |
| `orgId`     | string | Yes      | Your Basedash organization ID              |
| `exp`       | number | Yes      | Expiration timestamp (Unix seconds)        |
| `iat`       | number | Yes      | Issued-at timestamp (Unix seconds)         |
| `firstName` | string | No       | User's first name                          |
| `lastName`  | string | No       | User's last name                           |
| `role`      | string | No       | `ADMIN` or `MEMBER` (defaults to `MEMBER`) |

<Note>
  The `role` claim is only used when creating new members. It doesn't change the
  role of existing members.
</Note>

### Allowed origins

For security, you can restrict which domains can embed your Basedash organization. Configure this at **Settings → Embedding**

You can add specific origins (e.g., `https://app.example.com`) or use wildcard patterns to allow all subdomains of a domain (e.g., `https://*.example.com`).

<Note>
  Wildcard patterns must include a full domain name. Overly broad patterns like
  `https://*.com` are not allowed.
</Note>

If no origins are configured, embeds are allowed from any domain.

### Security best practices

<AccordionGroup>
  <Accordion title="Keep your JWT secret secure">
    Never expose the `jwtSecret` in client-side code. Generate JWTs only on your
    backend.
  </Accordion>

  <Accordion title="Use short token expiration">
    JWTs should expire within 10-60 minutes. Users only need a valid token when
    loading the iframe—once authenticated, they use a Basedash session cookie.
  </Accordion>

  <Accordion title="Configure allowed origins">
    Always set `embedAllowedOrigins` in production to prevent unauthorized sites
    from embedding your organization.
  </Accordion>

  <Accordion title="Use read-only database credentials">
    When connecting data sources, use database credentials with read-only
    permissions to minimize risk.
  </Accordion>

  <Accordion title="Validate user access server-side">
    Before generating a JWT, verify that the current user should have access to
    the embedded analytics.
  </Accordion>
</AccordionGroup>

### Troubleshooting

<AccordionGroup>
  <Accordion title="Missing JWT parameter">
    **Error**: "The embed URL is missing the required JWT token"

    Make sure your iframe `src` includes the `?jwt=` query parameter with your signed token.
  </Accordion>

  <Accordion title="Embedding not enabled">
    **Error**: "Embedding is not enabled for this organization"

    Enable embedding when creating the organization by setting `fullEmbedEnabled: true`, or update an existing organization via the API.
  </Accordion>

  <Accordion title="Origin not allowed">
    **Error**: "This embed is not authorized to load from \[origin]"

    Add your domain to the `embedAllowedOrigins` array. You can add a specific origin (e.g., `https://app.example.com`) or use a wildcard pattern to allow all subdomains (e.g., `https://*.example.com`).
  </Accordion>

  <Accordion title="Authentication failed (invalid or expired JWT)">
    **Error**: "The JWT token is invalid or has expired"

    * Verify you're using the correct `jwtSecret` for the organization
    * Check that the JWT hasn't expired (tokens should be short-lived)
    * Ensure you're signing with the HS256 algorithm
    * Verify the clock on your server is accurate (JWT validation allows 30 seconds of clock skew)
  </Accordion>

  <Accordion title="Missing Referer header">
    **Error**: "Unable to verify the request origin"

    This happens when allowed origins are configured but the browser doesn't send a Referer header. Check that:

    * Your page doesn't have `Referrer-Policy: no-referrer`
    * You're not loading the iframe from a `file://` URL during development
  </Accordion>

  <Accordion title="Organization not found">
    **Error**: "The organization ID in your JWT does not match any Basedash organization"

    Verify that the `orgId` claim in your JWT matches an existing Basedash organization ID.
  </Accordion>
</AccordionGroup>

## Related pages

* [API reference](/api-reference/overview)
* [Filters and variables](/features/filters-and-variables) - Includes secure filtering for locking filter values on public dashboards
* [Dashboards](/features/dashboards)
* [Chat](/features/chat)
