Embedding is available to all Lightdash Cloud users, get in touch to have this feature enabled in your account.
Overview
This document provides complete API reference for JWT token structure and configuration options used by both embedding methods (iframe and React SDK).
For method-specific implementation details, see:
For step-by-step guides, see:
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 JWT tokens for embedding content. This secret acts like a password that encrypts and signs your tokens.
Keep your embed secret secure! Store it as an environment variable and never expose it in frontend code. Always generate tokens server-side.
You can regenerate the secret by clicking Generate new secret. If you do this, all previously generated embed URLs will be invalidated immediately.
JWT token structure
All embedding methods use JWT tokens to authenticate and configure embedded content. The token structure includes three main parts:
Common fields
All tokens share these fields:
{
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:
{
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:
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:
Chart embedding is only available via the React SDK. iframe embedding for charts is not currently supported. See the React SDK reference for details.
{
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,
},
}
Chart tokens use contentId (the saved chart UUID) instead of dashboardUuid. Chart embeds are scoped to the specific chart and cannot access other content.
Example:
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.
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.
dashboardFiltersInteractivity: {
enabled: 'all',
}
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.
dashboardFiltersInteractivity: {
enabled: 'some',
allowedFilters: ['filter-uuid-1', 'filter-uuid-2'],
}
Configuring in the UI:
Will result in some filters in the dashboard for users to interact with:
Filters applied but hidden:
Filters are applied to the dashboard, but users cannot see or modify them.
dashboardFiltersInteractivity: {
enabled: 'none',
}
Parameter interactivity
Controls whether users can modify dashboard parameters.
parameterInteractivity?: {
enabled: boolean,
}
When enabled, users can change parameter values in the dashboard UI.
Export options
Control what users can export from embedded content.
{
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
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
Date zoom
Allows users to zoom into time-series data by changing granularity.
When enabled on time-series charts, users can click on data points to zoom to finer granularities (year → quarter → month → week → day).
Explore from here
Enables navigation from dashboard charts to the explore view.
When enabled, users see “Explore from here” in chart tile menus. This opens the full query builder with the chart’s configuration pre-loaded.
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.
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).
Allowed content
Allowed dashboards
Only dashboards added to the “allowed dashboards” list can be embedded.
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.
Chart embeds provide more granular access control than dashboards. Each chart must be individually allowed.
User attributes
User attributes enable row-level security by filtering data based on user properties.
userAttributes?: {
[attributeName: string]: string,
}
Example:
{
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.
Pass user information to track who’s viewing embedded content.
user?: {
externalId?: string, // Your internal user ID
email?: string, // User's email address
}
This metadata appears in query tags for usage analytics. If you don’t provide an externalId, Lightdash automatically generates one based on the embed token.
Example:
{
user: {
externalId: 'user-12345',
email: 'jane@example.com',
}
}
Token expiration
JWT tokens should have short expiration times for security. Use your JWT library’s expiration parameter:
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)
Always generate tokens server-side with short expiration times. Never generate long-lived tokens in frontend code.
Code examples
Node.js
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}/dashboard/dashboard-uuid#${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
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/dashboard-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
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/dashboard-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
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/dashboard/dashboard-uuid#%s",
projectUuid, dashboardToken
);
Security best practices
Never expose embed secret
// ❌ 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
// ❌ 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
// ✅ 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
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
// ✅ Filter data by user's tenant
{
userAttributes: {
tenant_id: user.tenantId, // From server-side user object
}
}
See User attributes reference for complete implementation guide.
See also