Skip to main content
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.
Try it and see the code. embed.lightdash.com is an interactive demo and helper that lets you preview embedded content and generate a JWT for your own project. For a working end-to-end example, see the example embed app on GitHub — a Node.js app that mints tokens server-side and renders a Lightdash dashboard.

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
For JWT structure and configuration options, see the embedding reference.
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

The 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:
The JWT must use 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:
The JWT must use 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:
The JWT must use 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

  1. Get your project UUID - Found in Lightdash project settings
  2. Get dashboard ID - Dashboard UUID or slug
  3. Generate JWT - See embedding reference for token structure
  4. Construct URL - Combine parts with hash fragment

URL with user attributes

For row-level security, include user attributes in the JWT:
See User attributes reference for complete guide.

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:
The embed 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 use theme, backgroundColor, and timezone together:
All URL parameters are optional. Existing embed URLs without these parameters behave exactly as before — light theme, default background, and the chart or project query timezone.

Building themed URLs

Embedding in HTML

Basic iframe

The simplest way to embed is with a standard HTML 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)
Method 2: Modern CSS aspect-ratio (16:9)
Method 3: Fixed viewport percentage

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:

Security considerations

iframes provide natural security isolation, but you can add additional restrictions:
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)
Template (EJS)

Single-page apps (SPA)

Generate URLs via API when component mounts: React example

Static 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:
  1. Create a server endpoint that generates embed URLs
  2. Use iframe embed code with dynamic URL
  3. Refresh tokens via JavaScript when expired
WordPress shortcode example:
Tools with an embed block that takes a URL, such as Notion’s /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. 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.
  1. Go to Settings → Embed and open the tab for the content type.
  2. Pick the content, an expiry, and the interactivity and user attributes the viewer should get.
  3. Click Generate & copy URL, then send the URL.
The link has the same limits as the embed:
  • 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 with window.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 postMessage turned 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 targetOrigin query parameter, before the #, set to that same origin: ?targetOrigin=https%3A%2F%2Fapp.example.com.

Token refresh

JWTs expire after the time specified in expiresIn. Handle token expiration:

Option 1: Long-lived tokens

For public or semi-public dashboards, use longer expiration:
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:

Option 3: Backend proxy

Create a backend endpoint that serves a static iframe URL but generates fresh tokens:
Then use:
The proxy also gives a screen that can’t run code, such as a kiosk or a TV dashboard, a URL that never changes or expires. Anyone who can reach the endpoint gets the content, so protect it with authentication or restrict it to known IP addresses. To filter each screen’s data, map a value from the path, such as a location ID, to a user attribute in the token.

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 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 despite dashboardFiltersInteractivity: { enabled: 'all' } Solutions:
  • Verify JWT includes correct interactivity settings
  • Check browser console for JavaScript errors
  • Ensure iframe isn’t using sandbox attribute without allow-forms