Embedding is available to all Lightdash Cloud users, get in touch to have this feature enabled in your account.
Overview
iframe embedding is the simplest way to embed Lightdash dashboards in your application. It requires no special libraries, dependencies, or CORS configuration—just generate a JWT token and construct an embed URL.
iframe embedding is only available for dashboards. Chart embedding requires the React SDK.
Benefits of iframe embedding
- Simple integration - Standard HTML iframe element, works anywhere
- No dependencies - No JavaScript libraries or SDK installation required
- No CORS configuration - Unlike the React SDK, iframes don’t require CORS setup
- Universal compatibility - Works in any web environment (React, Vue, Angular, vanilla HTML)
- Secure - JWT token in URL hash fragment isn’t sent to server or logged
When to use iframe embedding
- Quick integration without adding dependencies
- Non-React applications
- Content management systems (WordPress, Webflow, etc.)
- Simple HTML pages or static sites
- When you don’t need programmatic control (filters, callbacks)
When to use React SDK instead
Consider the React SDK if you need:
- Programmatic filters (apply filters via props)
- Callbacks (e.g., onExplore for analytics)
- Seamless React integration
- TypeScript type definitions
For JWT token structure and configuration options, see the embedding reference.
iframe URL patterns
All embed URLs follow this pattern: https://your-instance.lightdash.cloud/embed/{projectUuid}/{contentType}/{contentId}#{jwtToken}
The JWT token is passed in the URL hash fragment (#token) for security—it’s not sent to the server in requests or logged in browser history.
Dashboard URL
https://your-instance.lightdash.cloud/embed/{projectUuid}/dashboard/{dashboardUuid}#{jwtToken}
Using dashboard slug instead of UUID:
https://your-instance.lightdash.cloud/embed/{projectUuid}/dashboard/{dashboardSlug}#{jwtToken}
Example:
https://app.lightdash.cloud/embed/abc-123-def-456/dashboard/my-sales-dashboard#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
The JWT token in the hash fragment is NOT sent to the server with HTTP requests and does NOT appear in server logs or browser history, providing an additional security layer.
Chart embedding via iframe is not currently supported. Charts can only be embedded using the React SDK.
URL construction
Building the embed URL
- Get your project UUID - Found in Lightdash project settings
- Get dashboard ID - Dashboard UUID or slug
- Generate JWT token - See embedding reference for token structure
- Construct URL - Combine parts with hash fragment
import jwt from 'jsonwebtoken';
const LIGHTDASH_EMBED_SECRET = process.env.LIGHTDASH_EMBED_SECRET;
const instanceUrl = 'https://app.lightdash.cloud';
const projectUuid = 'your-project-uuid';
const dashboardUuid = 'your-dashboard-uuid';
// Generate JWT token
const token = jwt.sign({
content: {
type: 'dashboard',
dashboardUuid: dashboardUuid,
canExportCsv: true,
},
}, LIGHTDASH_EMBED_SECRET, { expiresIn: '1h' });
// Build embed URL
const embedUrl = `${instanceUrl}/embed/${projectUuid}/dashboard/${dashboardUuid}#${token}`;
console.log(embedUrl);
URL with user attributes
For row-level security, include user attributes in the JWT token:
const token = jwt.sign({
content: {
type: 'dashboard',
dashboardUuid: 'your-dashboard-uuid',
},
userAttributes: {
tenant_id: user.tenantId, // Filter data by tenant
region: user.region,
},
}, LIGHTDASH_EMBED_SECRET, { expiresIn: '1h' });
const embedUrl = `https://app.lightdash.cloud/embed/${projectUuid}/dashboard/${dashboardUuid}#${token}`;
See User attributes reference for complete guide.
Embedding in HTML
Basic iframe
The simplest way to embed is with a standard HTML iframe:
<iframe
src="https://app.lightdash.cloud/embed/project-uuid/dashboard/dashboard-uuid#jwt-token"
width="100%"
height="600"
frameborder="0"
style="border: none;"
></iframe>
Recommended attributes
<iframe
src="https://app.lightdash.cloud/embed/..."
width="100%"
height="600"
frameborder="0"
style="border: none;"
loading="lazy"
title="Lightdash Dashboard"
allowfullscreen
></iframe>
Attributes explained:
width="100%" - Makes iframe responsive to container width
height="600" - Fixed height (adjust based on content)
frameborder="0" - Removes default border (legacy)
style="border: none;" - Removes border (modern CSS)
loading="lazy" - Defers loading until iframe is visible
title="..." - Accessibility: Describes iframe content for screen readers
allowfullscreen - Enables fullscreen mode (if your dashboard uses it)
Responsive iframes
To make iframes maintain aspect ratio and scale responsively:
Method 1: Aspect ratio wrapper (16:9)
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
<iframe
src="https://app.lightdash.cloud/embed/..."
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none;"
frameborder="0"
allowfullscreen
></iframe>
</div>
Method 2: Modern CSS aspect-ratio (16:9)
<iframe
src="https://app.lightdash.cloud/embed/..."
style="aspect-ratio: 16/9; width: 100%; border: none;"
frameborder="0"
allowfullscreen
></iframe>
Method 3: Fixed viewport percentage
<iframe
src="https://app.lightdash.cloud/embed/..."
style="width: 100%; height: 80vh; border: none;"
frameborder="0"
></iframe>
Dynamic height
iframes have fixed height by default. For dynamic height based on content:
Lightdash embeds do not currently support automatic height adjustment via postMessage. Use a fixed height or viewport-based height (e.g., 80vh).
Recommended approach for dashboards with unknown content:
<iframe
src="https://app.lightdash.cloud/embed/..."
style="width: 100%; min-height: 600px; height: 90vh; border: none;"
frameborder="0"
></iframe>
Security considerations
iframes provide natural security isolation, but you can add additional restrictions:
<iframe
src="https://app.lightdash.cloud/embed/..."
sandbox="allow-scripts allow-same-origin allow-forms allow-downloads"
style="width: 100%; height: 600px; border: none;"
></iframe>
sandbox attributes:
allow-scripts - Required for Lightdash to function
allow-same-origin - Required for Lightdash to function
allow-forms - Required for filter interactions
allow-downloads - Required if you enable CSV/image exports
The sandbox attribute provides additional security but may restrict functionality. Test thoroughly if you use it.
Common patterns
Server-side rendering
Generate embed URLs in your server-side templates:
Express (Node.js)
app.get('/dashboard', authenticateUser, async (req, res) => {
const user = await getUser(req.user.id);
const token = jwt.sign({
content: {
type: 'dashboard',
dashboardUuid: 'dashboard-uuid',
},
userAttributes: {
tenant_id: user.tenantId,
},
}, process.env.LIGHTDASH_EMBED_SECRET, { expiresIn: '1h' });
const embedUrl = `https://app.lightdash.cloud/embed/${projectUuid}/dashboard/dashboard-uuid#${token}`;
res.render('dashboard', { embedUrl });
});
Template (EJS)
<div class="dashboard-container">
<iframe
src="<%= embedUrl %>"
width="100%"
height="600"
frameborder="0"
style="border: none;"
></iframe>
</div>
Single-page apps (SPA)
Generate URLs via API when component mounts:
React example
function EmbeddedDashboard() {
const [embedUrl, setEmbedUrl] = useState(null);
useEffect(() => {
fetch('/api/dashboard-embed-url')
.then(res => res.json())
.then(data => setEmbedUrl(data.url));
}, []);
if (!embedUrl) return <div>Loading...</div>;
return (
<iframe
src={embedUrl}
width="100%"
height="600"
frameBorder="0"
style={{ border: 'none' }}
/>
);
}
Static sites
For static sites, generate URLs at build time or use edge functions:
Next.js (server component)
import jwt from 'jsonwebtoken';
async function DashboardPage() {
// Generate at request time
const token = jwt.sign({
content: {
type: 'dashboard',
dashboardUuid: 'dashboard-uuid',
},
}, process.env.LIGHTDASH_EMBED_SECRET, { expiresIn: '24h' });
const embedUrl = `https://app.lightdash.cloud/embed/project-uuid/dashboard/dashboard-uuid#${token}`;
return (
<iframe
src={embedUrl}
width="100%"
height="600"
frameBorder="0"
style={{ border: 'none' }}
/>
);
}
Content management systems
Embed in WordPress, Webflow, or other CMS:
- Create a server endpoint that generates embed URLs
- Use iframe embed code with dynamic URL
- Refresh tokens via JavaScript when expired
WordPress shortcode example:
function lightdash_embed_shortcode($atts) {
$atts = shortcode_atts(array(
'dashboard' => '',
), $atts);
$embed_url = generate_lightdash_url($atts['dashboard']);
return '<iframe src="' . esc_url($embed_url) . '" width="100%" height="600" frameborder="0" style="border: none;"></iframe>';
}
add_shortcode('lightdash', 'lightdash_embed_shortcode');
Token refresh
JWT tokens expire after the time specified in expiresIn. Handle token expiration:
Option 1: Long-lived tokens
For public or semi-public dashboards, use longer expiration:
jwt.sign(payload, secret, { expiresIn: '7d' }) // 7 days
Long-lived tokens are convenient but less secure. Use only when appropriate for your use case.
Option 2: Regenerate URL on expiration
Detect when iframe shows “Token expired” error and reload with new URL:
function refreshEmbed() {
fetch('/api/dashboard-embed-url')
.then(res => res.json())
.then(data => {
document.getElementById('dashboard-iframe').src = data.url;
});
}
// Refresh before expiration (e.g., every 50 minutes for 1-hour tokens)
setInterval(refreshEmbed, 50 * 60 * 1000);
Option 3: Backend proxy
Create a backend endpoint that serves a static iframe URL but generates fresh tokens:
app.get('/embed-proxy/dashboard/:dashboardUuid', authenticateUser, (req, res) => {
const token = jwt.sign({
content: {
type: 'dashboard',
dashboardUuid: req.params.dashboardUuid,
},
}, process.env.LIGHTDASH_EMBED_SECRET, { expiresIn: '1h' });
const embedUrl = `https://app.lightdash.cloud/embed/${projectUuid}/dashboard/${req.params.dashboardUuid}#${token}`;
// Redirect to actual embed URL
res.redirect(embedUrl);
});
Then use:
<iframe src="/embed-proxy/dashboard/dashboard-uuid"></iframe>
Troubleshooting
Token not working
Issue: iframe shows “Invalid token” or “Token expired”
Solutions:
- Verify embed secret matches between token generation and Lightdash
- Check token hasn’t expired (
expiresIn in jwt.sign)
- Ensure JWT payload structure matches embedding reference
- Test token expiration:
jwt.decode(token) and check exp field
Content not displaying
Issue: iframe is blank or shows loading indefinitely
Solutions:
- Check browser console for errors
- Verify dashboard/chart UUID is correct
- Ensure content is added to “allowed dashboards/charts” in Lightdash settings
- Check project UUID is correct
- Try accessing embed URL directly in browser to see error message
CORS errors
Issue: Browser console shows CORS errors
Solution:
- iframes should NOT have CORS issues (CORS only affects React SDK)
- If you see CORS errors with iframes, you may be using fetch/XHR to load content instead of iframe
- Use standard iframe src attribute, not JavaScript-based loading
URL encoding issues
Issue: JWT token or URL appears malformed
Solutions:
- Don’t URL-encode the JWT token in the hash fragment
- If constructing URLs in templates, ensure proper escaping:
<!-- Good -->
<iframe src="https://app.lightdash.cloud/embed/...#<%= token %>"></iframe>
<!-- Bad - Don't URL encode token -->
<iframe src="https://app.lightdash.cloud/embed/...#<%= encodeURIComponent(token) %>"></iframe>
Dashboard filters not working
Issue: Users can’t interact with filters despite dashboardFiltersInteractivity: { enabled: 'all' }
Solutions:
- Verify JWT token includes correct interactivity settings
- Check browser console for JavaScript errors
- Ensure iframe isn’t using
sandbox attribute without allow-forms
See also