Embedding requires the embedding add-on for Cloud Pro or Enterprise. Get in touch to add embedding to your plan. For self-hosted deployments, follow embedding setup.
Overview
iframe embedding is the simplest way to embed Lightdash dashboards, charts, standalone data apps, and AI agents in your application. It requires no special libraries, dependencies, or CORS configuration—just generate a JWT and construct an embed URL. To compare iframe embedding with the other methods for each content type, see the embedding quickstart.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 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 for saves, filter changes, and errors (
onEvent,onError) - Seamless React integration
- TypeScript type definitions
theme and backgroundColor (below) are the only built-in style overrides for iframe embeds — the fontFamily style is a React SDK prop. For font or other visual customization on an iframe-embedded dashboard, see Known limitations.iframe URL patterns
All embed URLs follow this pattern:https://your-instance.lightdash.cloud/embed/{projectUuid}#{jwtToken}
The JWT 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
dashboardUuid is specified inside the JWT payload as content.dashboardUuid, not in the URL path.
Example:
Data app URL
Standalone data app embeds put both the project UUID and the app UUID in the path:content.type: 'dataApp' and the same appUuid as the URL. A token for one app cannot render any other app.
Example:
Chart URL
Chart embeds put both the project UUID and the chart UUID in the path:content.type: 'chart' and the same chart UUID as content.contentId. A token for one chart cannot render any other chart.
Example:
AI agent URL
AI agent embeds put the project UUID and the agent UUID in the path:content.type: 'aiAgent' for the same agent. To open an existing conversation, add the thread UUID after /threads; see Resuming a thread from an iframe embed.
Because the JWT sits in the hash fragment, it is never sent to the server with HTTP requests and never appears in server logs or browser history.
URL construction
Building the embed URL
- Get your project UUID - Found in Lightdash project settings
- Get dashboard ID - Dashboard UUID or slug
- Generate JWT - See embedding reference for token structure
- Construct URL - Combine parts with hash fragment
URL with user attributes
For row-level security, include user attributes in the JWT:URL parameters
Embedded dashboards support a few optional URL parameters that customize the visual appearance and the query timezone of the session. These are added as query parameters before the hash fragment.URL format
theme
Sets the color scheme for the embedded content.
Example:
backgroundColor
Sets a custom background color on the embed and its dashboard header, including tabs and the filter bar. Accepts bare hex codes without the # prefix, or transparent to show the host application’s background. The # is automatically prepended to hex codes.
Other CSS color formats (named colors,
rgb(), hsl(), etc.) are not supported. Omit the parameter to use the theme’s default backgrounds. Dashboard tile backgrounds are unchanged.
Example:
timezone
The embed timezone param is available to all organizations. On self-hosted instances where timezone support is disabled with LIGHTDASH_ENABLE_TIMEZONE_SUPPORT=false, the param is ignored and the session falls back to the chart pin or project default.
Sets a per-session query timezone for the embed. Use it when one embedded dashboard needs to render in different timezones for different viewers (for example, a multi-tenant app where each tenant has its own timezone), without creating separate charts per timezone.
Accepts an IANA timezone name (e.g. America/Los_Angeles, Europe/Berlin, Asia/Tokyo). The value is used for dashboard tile queries, inherited totals and subtotals, and filter autocomplete in that session.
URL-encode the value so the / is escaped (America%2FLos_Angeles); the plain America/Los_Angeles form is also accepted. A value that isn’t a valid IANA name returns a 400 error.
Example:
timezone param sits at the top of the timezone resolution priority for that session and overrides any per-chart timezone pin. It only applies to iframe embed URLs, including ones opened as a link, not the React SDK, where the host app’s URL is unrelated to the embed.
Combining parameters
You can usetheme, backgroundColor, and timezone together:
Building themed URLs
Embedding in HTML
Basic iframe
The simplest way to embed is with a standard HTML iframe:Recommended attributes
width="100%"- Makes iframe responsive to container widthheight="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 visibletitle="..."- Accessibility: Describes iframe content for screen readersallowfullscreen- 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)Dynamic height
iframes have fixed height by default. For dynamic height based on content: Recommended approach for dashboards with unknown content:Security considerations
iframes provide natural security isolation, but you can add additional restrictions:allow-scripts- Required for Lightdash to functionallow-same-origin- Required for Lightdash to functionallow-forms- Required for filter interactionsallow-downloads- Required if you enable CSV/image exports
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)Single-page apps (SPA)
Generate URLs via API when component mounts: React exampleStatic sites
For static sites, generate URLs at build time or use edge functions: Next.js (server component)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
/embed block, can show a dashboard from its embed URL. Paste the full URL, including the JWT after the #. The page has no way to refresh the token, so the embed stops working when the JWT expires; generate the URL with a long-lived token and replace it before it expires.
Open an embed URL as a link
The generated embed URL can also be used directly as a temporary link, useful for sharing content with people that don’t have a Lightdash account. When they open the link, the browser will render the iframe itself.- Go to Settings → Embed and open the tab for the content type.
- Pick the content, an expiry, and the interactivity and user attributes the viewer should get.
- Click Generate & copy URL, then send the URL.
- Anyone with the URL can open it until the token expires. You can’t see who has it or revoke a single URL; regenerating the embed secret invalidates every embed URL in the project.
- The viewer sees exactly what the embed shows. A chart, for example, has no CSV or image export; see chart embed limitations.
Listen for embed events
An iframe embed can send events to your page withwindow.postMessage. Each message has a type, such as lightdash:filterChanged, lightdash:tabChanged, lightdash:allTilesLoaded, lightdash:chartSaved or lightdash:locationChanged, plus a payload and a timestamp. Payloads are the same as for the React SDK events; locationChanged, which only iframe embeds send, carries { pathname, search, href }.
Messages are only sent when both of these are true:
- Your instance has embed events with
postMessageturned on, and your page’s exact origin is in its iframe embedding domains. See Events from the AI agent for the Cloud and self-hosted setup. - The embed URL has a
targetOriginquery parameter, before the#, set to that same origin:?targetOrigin=https%3A%2F%2Fapp.example.com.
Token refresh
JWTs expire after the time specified inexpiresIn. Handle token expiration:
Option 1: Long-lived tokens
For public or semi-public dashboards, use longer expiration:Option 2: Regenerate URL on expiration
Detect when iframe shows “Token expired” error and reload with new URL:Option 3: Backend proxy
Create a backend endpoint that serves a static iframe URL but generates fresh tokens: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 (
expiresInin jwt.sign) - Ensure JWT payload structure matches embedding reference
- Test token expiration:
jwt.decode(token)and checkexpfield
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 or URL appears malformed Solutions:- Don’t URL-encode the JWT in the hash fragment
- If constructing URLs in templates, ensure proper escaping:
Dashboard filters not working
Issue: Users can’t interact with filters despitedashboardFiltersInteractivity: { enabled: 'all' }
Solutions:
- Verify JWT includes correct interactivity settings
- Check browser console for JavaScript errors
- Ensure iframe isn’t using
sandboxattribute withoutallow-forms