> ## Documentation Index
> Fetch the complete documentation index at: https://lightdash-mintlify-40ee5f93.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Embedding reference

> Complete API reference for embedding Lightdash content securely using JWTs

<Info>
  Embedding is available to all Lightdash Cloud users and Enterprise On-Prem customers. [Get in touch](https://lightdash.typeform.com/to/BujU5wg5) to have this feature enabled in your account.
</Info>

## Overview

This document provides complete API reference for JWT structure and configuration options used across all embedding and sharing methods.

Lightdash supports three ways to share embedded content:

* **Shareable URL** — Generate a link that anyone can open directly in their browser, no iframe or SDK needed. Ideal for sharing dashboards with external users like clients or partners.
* **iframe embedding** — Embed dashboards inside your own web pages using a standard `<iframe>` tag.
* **React SDK** — Embed dashboards and charts in React/Next.js apps with full programmatic control.

All three methods use the same JWT-based authentication described below.

**For method-specific implementation details, see:**

* [iframe embedding reference](/references/iframe-embedding) - URL patterns, HTML embedding
* [React SDK reference](/references/react-sdk) - React components, props, TypeScript

**For step-by-step guides, see:**

* [Embedding quickstart](/guides/embedding/how-to-embed-content)
* [Embedding dashboards](/guides/embedding/dashboards)
* [Embedding charts](/guides/embedding/charts)

Embedded Lightdash content is available to view by anyone (not just folks with a Lightdash login). Content is secured using JWT (JSON Web Tokens) with configurable expiration times.

## Known limitations

* Embedding only works for dashboards and charts directly. To embed explores, use the `canExplore` flag in a dashboard.
* The **Filter dashboard to** option when clicking on individual chart segments will not work on embedded dashboards.

If you're interested in embedding and one or more of these items are blockers, please reach out.

## Embed secret

The embed secret is used to generate JWTs for embedding content. This secret acts like a password that encrypts and signs your tokens.

<Frame>
  <img src="https://mintcdn.com/lightdash-mintlify-40ee5f93/63XehSaJatKT9ceA/images/guides/embedding/embed-create-secret-fbb86d6bcb70f4d004c48adb3c107922.png?fit=max&auto=format&n=63XehSaJatKT9ceA&q=85&s=f4e00cc69fac4ebf7cf78f2e236811bc" alt="" width="1386" height="778" data-path="images/guides/embedding/embed-create-secret-fbb86d6bcb70f4d004c48adb3c107922.png" />
</Frame>

<Warning>
  Keep your embed secret secure! Store it as an environment variable and never expose it in frontend code. Always generate tokens server-side.
</Warning>

You can regenerate the secret by clicking `Generate new secret`. If you do this, all previously generated embed URLs will be invalidated immediately.

## JWT structure

All embedding methods use JWTs to authenticate and configure embedded content. The token structure includes three main parts:

### Common fields

All tokens share these fields:

```typescript theme={null}
{
  content: {
    // Content configuration (required)
    type: 'dashboard' | 'chart',
    projectUuid?: string,
    // ... type-specific fields
  },
  user?: {
    // User information for analytics (optional)
    externalId?: string,
    email?: string,
  },
  userAttributes?: {
    // User attributes for row-level filtering (optional)
    [attributeName: string]: string,
  },
  // Token expiration (handled by JWT library)
  exp?: number,
  iat?: number,
}
```

### Dashboard token

For embedding dashboards with multiple tiles, filters, and interactive features.

<Warning>
  All configuration options (`dashboardFiltersInteractivity`, `canExportCsv`, `canExplore`, etc.) must be nested **inside the `content` object** — not at the top level of the JWT payload. A common mistake is placing these properties at the root level, which will cause them to be silently ignored.
</Warning>

```typescript theme={null}
{
  content: {
    type: 'dashboard',

    // Dashboard identifier (required, use one)
    dashboardUuid?: string,
    dashboardSlug?: string,

    // Project identifier (optional)
    projectUuid?: string,

    // Filter interactivity
    dashboardFiltersInteractivity?: {
      enabled: 'all' | 'some' | 'none',  // Required
      allowedFilters?: string[],          // Required if enabled: 'some'
      hidden?: boolean,                   // Optional: hide filter UI
    },

    // Parameter interactivity
    parameterInteractivity?: {
      enabled: boolean,
    },

    // Export capabilities
    canExportCsv?: boolean,         // Allow CSV export
    canExportImages?: boolean,      // Allow image/PNG export
    canExportPagePdf?: boolean,     // Allow PDF export

    // Interactive features
    canDateZoom?: boolean,          // Allow date granularity zoom
    canExplore?: boolean,           // Allow "Explore from here"
    canViewUnderlyingData?: boolean, // Allow viewing raw data
  },

  // Optional: User information for query tracking
  user?: {
    externalId?: string,
    email?: string,
  },

  // Optional: User attributes for row-level filtering
  userAttributes?: {
    [attributeName: string]: string,
  },
}
```

**Example:**

```javascript theme={null}
import jwt from 'jsonwebtoken';

const token = jwt.sign({
  content: {
    type: 'dashboard',
    dashboardUuid: 'abc-123-def-456',
    dashboardFiltersInteractivity: {
      enabled: 'all',
    },
    canExportCsv: true,
    canExportImages: true,
    canExportPagePdf: true,
    canDateZoom: true,
    canExplore: true,
    canViewUnderlyingData: true,
  },
  user: {
    externalId: 'user-789',
    email: 'user@example.com',
  },
  userAttributes: {
    tenant_id: 'tenant-abc',
  },
}, SECRET, { expiresIn: '1h' });
```

### Chart token

For embedding individual saved charts with minimal UI:

<Warning>
  Chart embedding is **only available via the React SDK**. iframe embedding for charts is not currently supported. See the [React SDK reference](/references/react-sdk) for details.
</Warning>

```typescript theme={null}
{
  content: {
    type: 'chart',

    // Chart identifier (required)
    contentId: string,  // savedQueryUuid

    // Project identifier (optional)
    projectUuid?: string,

    // Preview mode (optional)
    isPreview?: boolean,

    // Permission scopes (optional)
    scopes?: string[],  // e.g., ['view:Chart']

    // Export capabilities
    canExportCsv?: boolean,         // Allow CSV export
    canExportImages?: boolean,      // Allow image/PNG export
    canViewUnderlyingData?: boolean, // Allow viewing raw data
  },

  // Optional: User information for query tracking
  user?: {
    externalId?: string,
    email?: string,
  },
}
```

<Info>
  Chart tokens use `contentId` (the saved chart UUID) instead of `dashboardUuid`. Chart embeds are scoped to the specific chart and cannot access other content.
</Info>

**Example:**

```javascript theme={null}
import jwt from 'jsonwebtoken';

const token = jwt.sign({
  content: {
    type: 'chart',
    contentId: 'saved-chart-uuid-789',
    scopes: ['view:Chart'],
    canExportCsv: true,
    canExportImages: false,
    canViewUnderlyingData: true,
  },
  user: {
    externalId: 'user-456',
    email: 'user@example.com',
  },
}, SECRET, { expiresIn: '24h' });
```

## Interactivity options reference

### Dashboard filters interactivity

Controls whether users can interact with dashboard filters.

```typescript theme={null}
dashboardFiltersInteractivity?: {
  enabled: 'all' | 'some' | 'none',
  allowedFilters?: string[],  // Filter UUIDs, required if enabled: 'some'
  hidden?: boolean,           // Hide filter UI but keep filters active
}
```

**Options:**

* `enabled: 'all'` - All dashboard filters are visible and interactive
* `enabled: 'some'` - Only filters listed in `allowedFilters` are interactive
* `enabled: 'none'` - Filters are applied but not visible or editable
* `hidden: true` - Filters are configurable at runtime, but UI is hidden (works with 'all' or 'some')

#### All filters available as interactive:

All filters configured on the dashboard will be shown in the embedded dashboard and interactive.

```javascript theme={null}
dashboardFiltersInteractivity: {
  enabled: 'all',
}
```

<Frame>
  <img src="https://mintcdn.com/lightdash-mintlify-40ee5f93/hfZNiLILDYGp_Pgi/images/references/embedding-preview-all-filters-bf5d00a3ef24a2370bc5ca56c8785e2d.png?fit=max&auto=format&n=hfZNiLILDYGp_Pgi&q=85&s=dc4fd2ea831f37d40bd170c683342ed5" alt="" width="1204" height="350" data-path="images/references/embedding-preview-all-filters-bf5d00a3ef24a2370bc5ca56c8785e2d.png" />
</Frame>

#### Specific filters only

Only the filters you select will be shown in the embedded dashboard for users to interact with. All dashboard filters are still applied.

```javascript theme={null}
dashboardFiltersInteractivity: {
  enabled: 'some',
  allowedFilters: ['filter-uuid-1', 'filter-uuid-2'],
}
```

Configuring in the UI:

<Frame>
  <img src="https://mintcdn.com/lightdash-mintlify-40ee5f93/hfZNiLILDYGp_Pgi/images/references/embedding-settings-some-filters-1658fe2f72623f85dee2eff322833ea8.png?fit=max&auto=format&n=hfZNiLILDYGp_Pgi&q=85&s=7dc499c1b6f3410bf2ada2703c2664c5" alt="" width="843" height="175" data-path="images/references/embedding-settings-some-filters-1658fe2f72623f85dee2eff322833ea8.png" />
</Frame>

Will result in some filters in the dashboard for users to interact with:

<Frame>
  <img src="https://mintcdn.com/lightdash-mintlify-40ee5f93/hfZNiLILDYGp_Pgi/images/references/embedding-preview-some-filters-9f13263ec3800c3e7f5e6079fa78cd66.png?fit=max&auto=format&n=hfZNiLILDYGp_Pgi&q=85&s=b7147b9dd12ee1569e4b54960fa87279" alt="" width="1204" height="360" data-path="images/references/embedding-preview-some-filters-9f13263ec3800c3e7f5e6079fa78cd66.png" />
</Frame>

#### Filters applied but hidden:

Filters are applied to the dashboard, but users cannot see or modify them.

```javascript theme={null}
dashboardFiltersInteractivity: {
  enabled: 'none',
}
```

<Frame>
  <img src="https://mintcdn.com/lightdash-mintlify-40ee5f93/hfZNiLILDYGp_Pgi/images/references/embedding-preview-no-filters-ced58145c215a0b17d86b50634a09057.png?fit=max&auto=format&n=hfZNiLILDYGp_Pgi&q=85&s=330534b27bc49f1107a0ea7c282ef1dd" alt="" width="1196" height="301" data-path="images/references/embedding-preview-no-filters-ced58145c215a0b17d86b50634a09057.png" />
</Frame>

### Parameter interactivity

Controls whether users can modify dashboard parameters.

```typescript theme={null}
parameterInteractivity?: {
  enabled: boolean,
}
```

When enabled, users can change parameter values in the dashboard UI.

### Export options

Control what users can export from embedded content.

```typescript theme={null}
{
  canExportCsv?: boolean,        // Download chart data as CSV files
  canExportImages?: boolean,     // Download charts as PNG images
  canExportPagePdf?: boolean,    // Download entire dashboard page as PDF (dashboards only)
}
```

**CSV Export:**

* Enables "Download CSV" in chart tile menus
* Each chart can be exported individually
* Exports the data shown in the visualization

<Frame>
  <img src="https://mintcdn.com/lightdash-mintlify-40ee5f93/hfZNiLILDYGp_Pgi/images/references/embedding-download-csv-2a4cbcfa350c6cefed880971090c117b.png?fit=max&auto=format&n=hfZNiLILDYGp_Pgi&q=85&s=f73ab7029ad88dc5cdcf5a821efe36be" alt="" width="573" height="189" data-path="images/references/embedding-download-csv-2a4cbcfa350c6cefed880971090c117b.png" />
</Frame>

**Image Export:**

* Enables "Download as image" in chart tile menus
* Exports charts as PNG files
* Captures current chart state

**PDF Export (dashboards only):**

* Enables print icon in dashboard header
* Exports entire dashboard page as PDF
* Includes all visible tiles

<Frame>
  <img src="https://mintcdn.com/lightdash-mintlify-40ee5f93/hfZNiLILDYGp_Pgi/images/references/embedding-print-61cb9f9107c6e4ae00fa3cde99dd572d.png?fit=max&auto=format&n=hfZNiLILDYGp_Pgi&q=85&s=373cb955e07ce51917ff234b2125a57a" alt="" width="830" height="289" data-path="images/references/embedding-print-61cb9f9107c6e4ae00fa3cde99dd572d.png" />
</Frame>

### Date zoom

Allows users to zoom into time-series data by changing granularity.

```typescript theme={null}
canDateZoom?: boolean
```

When enabled, users can change the date granularity of charts on the dashboard. See the [Date zoom guide](/guides/date-zoom) for complete documentation on this feature.

### Explore from here

Enables navigation from dashboard charts to the explore view.

```typescript theme={null}
canExplore?: boolean
```

When enabled, users see "Explore from here" in chart tile menus. This opens the full query builder with the chart's configuration pre-loaded.

<Frame>
  <img src="https://mintcdn.com/lightdash-mintlify-40ee5f93/hfZNiLILDYGp_Pgi/images/references/embedding-explore-from-here.png?fit=max&auto=format&n=hfZNiLILDYGp_Pgi&q=85&s=05d1d91a61c680ac1f78c94e965c1a37" alt="" width="704" height="299" data-path="images/references/embedding-explore-from-here.png" />
</Frame>

Users can:

* Modify dimensions and metrics
* Apply different filters
* Change chart types
* Run custom queries

Users cannot:

* Save charts
* Share results
* View SQL

### View underlying data

Allows users to view the raw data table behind visualizations.

```typescript theme={null}
canViewUnderlyingData?: boolean
```

When enabled, users can click on charts to open a modal showing the underlying data table. Data cannot be exported separately (use `canExportCsv` for that).

<Frame>
  <img src="https://mintcdn.com/lightdash-mintlify-40ee5f93/hfZNiLILDYGp_Pgi/images/references/embedding-view-underlying-data.png?fit=max&auto=format&n=hfZNiLILDYGp_Pgi&q=85&s=860a473298b467da93f8770c14b650a6" alt="" width="686" height="427" data-path="images/references/embedding-view-underlying-data.png" />
</Frame>

## Allowed content

### Allowed dashboards

Only dashboards added to the "allowed dashboards" list can be embedded.

<Frame>
  <img src="https://mintcdn.com/lightdash-mintlify-40ee5f93/63XehSaJatKT9ceA/images/guides/embedding/embed-add-dashboard-cb6bef5fde69ba9d6eaeb693744d75cc.png?fit=max&auto=format&n=63XehSaJatKT9ceA&q=85&s=abde5d9902084d097b1d3e015488b50f" alt="" width="881" height="403" data-path="images/guides/embedding/embed-add-dashboard-cb6bef5fde69ba9d6eaeb693744d75cc.png" />
</Frame>

You can use the "Allow all dashboards" toggle to bypass dashboard selection. When enabled, any dashboard in your project can be embedded.

### Allowed charts

Charts must be explicitly allowed for embedding. Add charts to the allowed list in your embed settings.

<Info>
  Chart embeds provide more granular access control than dashboards. Each chart must be individually allowed.
</Info>

### Default allow settings via environment variables

For self-hosted deployments, you can configure new project embeds to allow all dashboards and/or charts by default using environment variables:

| Variable                                | Description                                               | Default |
| --------------------------------------- | --------------------------------------------------------- | ------- |
| `EMBED_ALLOW_ALL_DASHBOARDS_BY_DEFAULT` | When creating new embeds, allow all dashboards by default | `false` |
| `EMBED_ALLOW_ALL_CHARTS_BY_DEFAULT`     | When creating new embeds, allow all charts by default     | `false` |

When these are set to `true`, new project embeddings will automatically have all dashboards or charts allowed without needing to manually configure the allowed content list.

See the [environment variables reference](/self-host/customize-deployment/environment-variables#embedding) for the complete list of embedding-related configuration options.

## User attributes

User attributes enable row-level security by filtering data based on user properties.

```typescript theme={null}
userAttributes?: {
  [attributeName: string]: string,
}
```

**Example:**

```javascript theme={null}
{
  userAttributes: {
    tenant_id: 'customer-123',
    region: 'us-west',
    department: 'sales',
  }
}
```

User attributes filter tables that have `sql_filter` configurations in dbt. See the complete [User attributes guide](/references/workspace/user-attributes).

## User metadata

Pass user information to track who's viewing embedded content.

```typescript theme={null}
user?: {
  externalId?: string,  // Your internal user ID
  email?: string,       // User's email address
}
```

This metadata appears in [query tags](/references/workspace/usage-analytics#query-tags) for usage analytics. If you don't provide an `externalId`, Lightdash automatically generates one based on the embed token.

**Example:**

```javascript theme={null}
{
  user: {
    externalId: 'user-12345',
    email: 'jane@example.com',
  }
}
```

## Token expiration

JWTs should have short expiration times for security. Use your JWT library's expiration parameter:

```javascript theme={null}
jwt.sign(payload, secret, { expiresIn: '1h' })
```

**Recommended expiration times:**

* **Development/testing**: `'24h'` or `'1 week'`
* **Production dashboards**: `'1h'` to `'4h'`
* **Production charts**: `'24h'` (if used in public pages)
* **Explore sessions**: `'4h'` to `'8h'` (longer for analysis sessions)

<Warning>
  Always generate tokens server-side with short expiration times. Never generate long-lived tokens in frontend code.
</Warning>

## Code examples

### Node.js

```javascript theme={null}
import jwt from 'jsonwebtoken';

const LIGHTDASH_EMBED_SECRET = process.env.LIGHTDASH_EMBED_SECRET;
const projectUuid = 'your-project-uuid';

// Dashboard embed
const dashboardToken = jwt.sign({
  content: {
    type: 'dashboard',
    dashboardUuid: 'dashboard-uuid',
    dashboardFiltersInteractivity: { enabled: 'all' },
    canExportCsv: true,
  },
  userAttributes: { tenant_id: 'tenant-123' },
}, LIGHTDASH_EMBED_SECRET, { expiresIn: '1h' });

const dashboardUrl = `https://app.lightdash.cloud/embed/${projectUuid}#${dashboardToken}`;

// Chart embed
const chartToken = jwt.sign({
  content: {
    type: 'chart',
    contentId: 'chart-uuid',
    canExportCsv: true,
  },
}, LIGHTDASH_EMBED_SECRET, { expiresIn: '24h' });

const chartUrl = `https://app.lightdash.cloud/embed/${projectUuid}/chart/chart-uuid#${chartToken}`;
```

### Python

```python theme={null}
import jwt
import datetime
import os

LIGHTDASH_EMBED_SECRET = os.getenv('LIGHTDASH_EMBED_SECRET')
project_uuid = 'your-project-uuid'

# Dashboard embed
dashboard_payload = {
    'content': {
        'type': 'dashboard',
        'dashboardUuid': 'dashboard-uuid',
        'dashboardFiltersInteractivity': { 'enabled': 'all' },
        'canExportCsv': True,
    },
    'userAttributes': { 'tenant_id': 'tenant-123' },
    'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}

dashboard_token = jwt.encode(dashboard_payload, LIGHTDASH_EMBED_SECRET, algorithm='HS256')
dashboard_url = f"https://app.lightdash.cloud/embed/{project_uuid}#{dashboard_token}"

# Chart embed
chart_payload = {
    'content': {
        'type': 'chart',
        'contentId': 'chart-uuid',
        'canExportCsv': True,
    },
    'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24)
}

chart_token = jwt.encode(chart_payload, LIGHTDASH_EMBED_SECRET, algorithm='HS256')
chart_url = f"https://app.lightdash.cloud/embed/{project_uuid}/chart/chart-uuid#{chart_token}"
```

### Ruby

```ruby theme={null}
require 'jwt'

lightdash_embed_secret = ENV['LIGHTDASH_EMBED_SECRET']
project_uuid = 'your-project-uuid'

# Dashboard embed
dashboard_payload = {
  content: {
    type: 'dashboard',
    dashboardUuid: 'dashboard-uuid',
    dashboardFiltersInteractivity: { enabled: 'all' },
    canExportCsv: true
  },
  userAttributes: { tenant_id: 'tenant-123' },
  exp: Time.now.to_i + 3600  # 1 hour
}

dashboard_token = JWT.encode(dashboard_payload, lightdash_embed_secret, 'HS256')
dashboard_url = "https://app.lightdash.cloud/embed/#{project_uuid}##{dashboard_token}"

# Chart embed
chart_payload = {
  content: {
    type: 'chart',
    contentId: 'chart-uuid',
    canExportCsv: true
  },
  exp: Time.now.to_i + 86400  # 24 hours
}

chart_token = JWT.encode(chart_payload, lightdash_embed_secret, 'HS256')
chart_url = "https://app.lightdash.cloud/embed/#{project_uuid}/chart/chart-uuid##{chart_token}"
```

### Java

```java theme={null}
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

String LIGHTDASH_EMBED_SECRET = System.getenv("LIGHTDASH_EMBED_SECRET");
String projectUuid = "your-project-uuid";

// Dashboard embed
Map<String, Object> dashboardContent = new HashMap<>();
dashboardContent.put("type", "dashboard");
dashboardContent.put("dashboardUuid", "dashboard-uuid");
dashboardContent.put("canExportCsv", true);

Map<String, Object> userAttrs = new HashMap<>();
userAttrs.put("tenant_id", "tenant-123");

Map<String, Object> dashboardClaims = new HashMap<>();
dashboardClaims.put("content", dashboardContent);
dashboardClaims.put("userAttributes", userAttrs);

String dashboardToken = Jwts.builder()
    .setClaims(dashboardClaims)
    .setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1 hour
    .signWith(SignatureAlgorithm.HS256, LIGHTDASH_EMBED_SECRET)
    .compact();

String dashboardUrl = String.format(
    "https://app.lightdash.cloud/embed/%s#%s",
    projectUuid, dashboardToken
);
```

## Security best practices

### Never expose embed secret

```javascript theme={null}
// ❌ BAD: Never do this
const token = jwt.sign(payload, 'my-secret-key');  // Hardcoded secret

// ✅ GOOD: Use environment variables
const token = jwt.sign(payload, process.env.LIGHTDASH_EMBED_SECRET);
```

### Generate tokens server-side only

```javascript theme={null}
// ❌ BAD: Don't generate tokens in frontend/browser
// This exposes your secret!

// ✅ GOOD: Create a backend API endpoint
app.get('/api/embed-token', authenticateUser, (req, res) => {
  const token = jwt.sign({
    content: { type: 'dashboard', dashboardUuid: 'xyz' },
    userAttributes: { tenant_id: req.user.tenantId },
  }, process.env.SECRET);

  res.json({ token });
});
```

### Use short-lived tokens

```javascript theme={null}
// ✅ GOOD: Tokens expire quickly
jwt.sign(payload, secret, { expiresIn: '1h' })

// ⚠️ CAUTION: Long-lived tokens are riskier
jwt.sign(payload, secret, { expiresIn: '30d' })
```

### Validate user ownership

```javascript theme={null}
app.get('/api/embed-token', authenticateUser, async (req, res) => {
  const user = await getUser(req.user.id);
  const requestedDashboard = req.query.dashboardId;

  // ✅ Verify user has access to dashboard
  if (!userHasAccessTo(user, requestedDashboard)) {
    return res.status(403).json({ error: 'Unauthorized' });
  }

  const token = jwt.sign({ content: { ... } }, secret);
  res.json({ token });
});
```

### Use user attributes for row-level security

```javascript theme={null}
// ✅ Filter data by user's tenant
{
  userAttributes: {
    tenant_id: user.tenantId,  // From server-side user object
  }
}
```

See [User attributes reference](/references/workspace/user-attributes) for complete implementation guide.

## See also

<CardGroup cols={2}>
  <Card title="iframe embedding reference" icon="code" href="/references/iframe-embedding">
    URL patterns and HTML embedding details
  </Card>

  <Card title="React SDK reference" icon="react" href="/references/react-sdk">
    Component API for React integration
  </Card>

  <Card title="Embedding quickstart" icon="rocket" href="/guides/embedding/how-to-embed-content">
    Get started with embedding in 5 minutes
  </Card>

  <Card title="User attributes" icon="shield-check" href="/references/workspace/user-attributes">
    Implement row-level security
  </Card>
</CardGroup>
