# AI agents Source: https://docs.lightdash.com/agents Ask questions in plain language and get governed answers, charts, dashboards, and data apps AI agents are available as an add-on for all plans. [View pricing](https://www.lightdash.com/pricing) Lightdash AI agents let your team ask questions in natural language and get answers built from your semantic layer — the saved dashboards, metrics, dimensions, joins, and descriptions you've already defined. An agent picks the relevant models and metrics, builds and runs the query with the right filters and [parameters](/semantic-layer/parameters), and returns the result as the chart, table, summary, or interactive data app that fits the question. Because every answer runs through the semantic layer, it respects the same project permissions and user attributes as the rest of Lightdash. You can run agents in the Lightdash app or in Slack, scope each one to a domain with tags, and improve them over time with verified answers, evaluations, and knowledge documents. The pages below cover setup, day-to-day use, governance, and the features that make agents more accurate. Looking for [**Autopilot**](/agents/autopilot)? Autopilot is a separate, admin-only agent that runs on a schedule to keep your project clean — fixing broken charts, flagging stale content, and suggesting new ones. It does not build charts, dashboards, or data apps from user questions like the conversational agents described here. ``` The [iframe embedding reference](/embed/iframe) covers URL parameters (including `theme`, `backgroundColor`, and `timezone`), responsive and dynamic sizing, token refresh, and troubleshooting. ## Embed with the React SDK The React SDK renders the dashboard as a component and adds programmatic filters, callbacks, custom styling, and edit mode. ```tsx theme={null} import Lightdash from '@lightdash/sdk'; function MyDashboard() { return ( ); } ``` See the [React SDK reference](/embed/react-sdk#lightdashdashboard) for installation, CORS setup, props, filters, theming, and localization. # How to embed data apps Source: https://docs.lightdash.com/embed/embed-data-apps Embed a Lightdash data app as standalone content in an iframe using a JWT-scoped preview token 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. **Try it and see the code.** [embed.lightdash.com](https://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](https://github.com/lightdash/example-embed-dashboard-with-nodejs-app) — a Node.js app that mints tokens server-side and renders a Lightdash dashboard. ## Overview Standalone data app embedding lets you render a [data app](/data-apps) on its own — no dashboard, no surrounding chrome — inside an iframe in your application. The app runs in the same sandboxed environment as it does in Lightdash, with every metric query proxied through Lightdash and authorized against the embed JWT. Use standalone embedding when you want to surface an entire data app as a feature in your product (for example, a customer-facing report or interactive narrative) rather than embedding it as one tile on a Lightdash dashboard. ### How it compares to other embed types | | Standalone data app | Data app tile on an embedded dashboard | Embedded chart | | --------------------- | ------------------------------------ | ---------------------------------------- | --------------- | | Content | One data app, full screen | One data app inside a dashboard | One saved chart | | JWT `content.type` | `dataApp` | `dashboard` with `canViewDataApps: true` | `chart` | | URL path | `/embed/{projectUuid}/app/{appUuid}` | `/embed/{projectUuid}` | React SDK only | | Filters / tile chrome | None | Dashboard filters and tile chrome | None | For tile-based embedding inside a dashboard, see [View data apps in dashboards](/embed/embed-dashboards#view-data-apps). ## Setup These steps assume you've already created a project embed secret — see the [embedding quickstart](/embed/set-up-embedding) if you haven't. ### 1. Enable the data app in embed settings Open **Settings → Embedding** for your project and use the **Allowed content** section to authorize the data apps you want to embed: * Toggle **Allow all data apps** to permit any app in the project, or * Add specific data apps to the allowlist. Standalone embedding is denied for any app that isn't on the allowlist (or covered by the "allow all" switch). This is enforced server-side regardless of what your JWT contains. ### 2. Mint a data app JWT Sign a JWT with `content.type: 'dataApp'` and the `appUuid` of the data app you want to embed. The JWT must be signed with your project's embed secret. ```javascript theme={null} import jwt from 'jsonwebtoken'; const token = jwt.sign( { content: { type: 'dataApp', projectUuid: 'your-project-uuid', appUuid: 'your-app-uuid', }, user: { externalId: 'user-123', email: 'user@example.com', }, userAttributes: { tenant_id: 'tenant-abc', }, }, LIGHTDASH_EMBED_SECRET, { expiresIn: '1h' }, ); ``` A `dataApp` token can only render the app named in its `appUuid` claim. It cannot be reused to render any other data app, chart, or dashboard. ### 3. Construct the embed URL Data app embeds use the path `/embed/{projectUuid}/app/{appUuid}` with the JWT in the URL hash fragment: ``` https://your-instance.lightdash.cloud/embed/{projectUuid}/app/{appUuid}#{token} ``` The hash fragment is never sent to the server or stored in browser history. ### 4. Render the iframe ```html theme={null} ``` You can copy ready-to-use Node, Python, and Go snippets from **Settings → Embedding → Preview → Data apps** in Lightdash. ## How access works A `dataApp` JWT is intentionally narrow: it authorizes the named app for the named project and nothing else. In practice, this means: * The token grants the embed user permission to **view the specified data app only**. * The data app can run arbitrary metric queries across the project's tables. Embedding an app means you are accepting project-wide column access for that token. * **Row-level filtering still applies.** User attributes on the JWT are enforced inside every query, just like they are for dashboard and chart embeds. SQL filters defined on your tables continue to apply. * A `dataApp` token **cannot** be used to load dashboards, charts, or the explore page. Each embed token is scoped to a single content type. Because the token grants project-wide column access for the app's queries, only mint `dataApp` tokens for audiences you trust with that data. Use [user attributes](/workspace-admin/user-attributes) to enforce row-level access. ## Limitations * Standalone data app embedding is iframe-only. There is no React SDK component for it yet. * The standalone embed has no filter bar or tile chrome — the app is rendered in isolation. * Interactivity options like `canExportCsv`, `canExplore`, and `dashboardFiltersInteractivity` do not apply to `dataApp` tokens. ## See also * [Data apps overview](/data-apps) * [Embedding reference (JWT structure)](/embed/reference) * [iframe embedding reference](/embed/iframe) * [Embed dashboards with data app tiles](/embed/embed-dashboards#view-data-apps) # How to embed the metrics catalog Source: https://docs.lightdash.com/embed/embed-metrics-catalog Let your users browse a project's metrics and dimensions without a Lightdash login 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. **Try it and see the code.** [embed.lightdash.com](https://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](https://github.com/lightdash/example-embed-dashboard-with-nodejs-app) — a Node.js app that mints tokens server-side and renders a Lightdash dashboard. ## Overview Metrics catalog embedding lets you drop the Lightdash metrics catalog directly into your application. Embedded users can browse the metrics defined in a project, search by name or category, preview a metric on a small chart, and — if you allow it — jump into Explore to slice the metric by any dimension in the model. Metrics catalog embeds use the same JWT-based security model as other embed types, with a dedicated `content.type: "metricsCatalog"` token. Dashboard, chart, and AI agent tokens cannot access the metrics catalog, and a metrics catalog token cannot render other embed surfaces. ### When to use metrics catalog embedding * Give customers a self-serve "what can I measure?" view of your semantic layer inside your product. * Surface a metric browser next to your own charts so users can discover related metrics. * Let embedded users start a new exploration from any metric without exposing your full Lightdash workspace. ### Available features Embedded metrics catalog supports: * Browsing all metrics in the project the JWT is scoped to * Searching and filtering by category * Previewing a metric with its default time-series chart * Continuing into an embedded Explore from any metric when `content.canExplore` is `true` * Saving Explore results into a fixed destination space via [write actions](/embed/reference#write-actions) * Row- and column-level filtering via [user attributes](/workspace-admin/user-attributes) ## Prerequisites Before you embed the metrics catalog, you need: * An embed secret for the project. See [Embedding quickstart](/embed/set-up-embedding). * At least one metric defined in the project's semantic layer. See [How to create metrics](/semantic-layer/metrics). * A React or Next.js host application — metrics catalog embedding is only available through the React SDK. * If you want embedded users to save charts from Explore, a destination space and a write actor. See [Write actions](/embed/reference#write-actions). ## Embed the metrics catalog with the React SDK The React SDK ships a `Lightdash.MetricsCatalog` component that renders the catalog. When the embedded viewer clicks **Explore from here** on a metric, the same component swaps in an embedded Explore view — the host page never has to change route. ```tsx theme={null} import Lightdash from '@lightdash/sdk'; function MyMetricsCatalog() { return ( ); } ``` See [`Lightdash.MetricsCatalog`](/embed/react-sdk#lightdashmetricscatalog) for the full prop list and styling options. ## Generate a metrics catalog embed token Metrics catalog embeds require a JWT with `content.type: "metricsCatalog"`. Generate it server-side using your embed secret; the [embedding reference](/embed/reference#metrics-catalog-token) has the complete token structure. ```javascript theme={null} import jwt from 'jsonwebtoken'; const token = jwt.sign({ content: { type: 'metricsCatalog', projectUuid: 'your-project-uuid', canExplore: true, }, writeActions: { serviceAccountUserUuid: 'service-account-user-uuid', spaceUuid: 'destination-space-uuid', }, userAttributes: { tenant_id: 'tenant-abc', // Row-level filtering for the embedded viewer }, user: { email: 'customer@example.com', }, }, process.env.LIGHTDASH_EMBED_SECRET, { expiresIn: '1h' }); ``` **Required fields:** * `content.type` — must be `"metricsCatalog"`. * `content.projectUuid` — pins the embed to a specific project. The catalog only lists metrics from this project. **Optional fields:** * `content.canExplore` — when `true`, embedded users can click **Explore from here** on any metric to open the embedded Explore view. Omit or set to `false` to keep the embed read-only. * `writeActions` — needed only when `canExplore` is `true` and you want embedded users to save charts they build from Explore. See [Write actions](/embed/reference#write-actions). * `userAttributes` — applies row- and column-level filters to catalog previews and Explore queries, identical to other embed types. * `user.email` / `user.externalId` — surfaced in audit and analytics for the embedded viewer. ## Controlling access Metrics catalog embeds honor the same access controls as other embed surfaces: * The catalog only shows metrics from the project named in `content.projectUuid`. * [User attributes](/workspace-admin/user-attributes) applied to the JWT hide metrics and dimensions whose `required_attributes` rules the embedded viewer does not satisfy, both in the catalog listing and in any Explore session launched from it. * Metric previews and Explore queries run with the write actor's permissions (when `writeActions` is set) plus any `userAttributes` filters, so embedded viewers only see rows and columns they are entitled to. * When `canExplore` is not set to `true`, the catalog is read-only — the **Explore from here** action is hidden and Explore routes reject the token. * Metrics catalog tokens are rejected by dashboard, chart, AI agent, and data app routes, and vice versa. ## Example: read-only metrics catalog To publish a browse-only catalog without Explore or chart saving, mint a token with just `type` and `projectUuid`: ```javascript theme={null} const token = jwt.sign({ content: { type: 'metricsCatalog', projectUuid: 'your-project-uuid', }, userAttributes: { tenant_id: 'tenant-abc', }, }, process.env.LIGHTDASH_EMBED_SECRET, { expiresIn: '1h' }); ``` Embedded users can search and preview metrics, but the **Explore from here** action is hidden and Explore routes reject the token. # Embedding with iframe Source: https://docs.lightdash.com/embed/iframe Complete reference for embedding Lightdash content using iframes with JWTs in URL hash fragments 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. **Try it and see the code.** [embed.lightdash.com](https://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](https://github.com/lightdash/example-embed-dashboard-with-nodejs-app) — 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** and standalone **data apps** in your application. It requires no special libraries, dependencies, or CORS configuration—just generate a JWT and construct an embed URL. iframe embedding supports [dashboards](/embed/embed-dashboards) and [data apps](/embed/embed-data-apps). [Chart embedding](/embed/embed-charts) requires the [React SDK](/embed/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 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](/embed/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 structure and configuration options, see the [embedding reference](/embed/reference). ## 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 ``` https://your-instance.lightdash.cloud/embed/{projectUuid}#{jwtToken} ``` The `dashboardUuid` is specified inside the JWT payload as `content.dashboardUuid`, not in the URL path. **Example:** ``` https://app.lightdash.cloud/embed/abc-123-def-456#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` ### Data app URL Standalone [data app](/embed/embed-data-apps) embeds put both the project UUID and the app UUID in the path: ``` https://your-instance.lightdash.cloud/embed/{projectUuid}/app/{appUuid}#{jwtToken} ``` 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:** ``` https://app.lightdash.cloud/embed/abc-123-def-456/app/app-uuid-789#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` 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. **Chart embedding via iframe is not currently supported.** Charts can only be embedded using the [React SDK](/embed/react-sdk). ## 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](/embed/reference#jwt-structure) for token structure 4. **Construct URL** - Combine parts with hash fragment ```javascript Node.js theme={null} 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 const token = jwt.sign({ content: { type: 'dashboard', dashboardUuid: dashboardUuid, canExportCsv: true, }, }, LIGHTDASH_EMBED_SECRET, { expiresIn: '1h' }); // Build embed URL const embedUrl = `${instanceUrl}/embed/${projectUuid}#${token}`; console.log(embedUrl); ``` ```python Python theme={null} import jwt import datetime LIGHTDASH_EMBED_SECRET = os.getenv('LIGHTDASH_EMBED_SECRET') instance_url = 'https://app.lightdash.cloud' project_uuid = 'your-project-uuid' dashboard_uuid = 'your-dashboard-uuid' # Generate JWT payload = { 'content': { 'type': 'dashboard', 'dashboardUuid': dashboard_uuid, 'canExportCsv': True, }, 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1) } token = jwt.encode(payload, LIGHTDASH_EMBED_SECRET, algorithm='HS256') # Build embed URL embed_url = f"{instance_url}/embed/{project_uuid}#{token}" print(embed_url) ``` ```ruby Ruby theme={null} require 'jwt' lightdash_embed_secret = ENV['LIGHTDASH_EMBED_SECRET'] instance_url = 'https://app.lightdash.cloud' project_uuid = 'your-project-uuid' dashboard_uuid = 'your-dashboard-uuid' # Generate JWT payload = { content: { type: 'dashboard', dashboardUuid: dashboard_uuid, canExportCsv: true }, exp: Time.now.to_i + 3600 } token = JWT.encode(payload, lightdash_embed_secret, 'HS256') # Build embed URL embed_url = "#{instance_url}/embed/#{project_uuid}##{token}" puts embed_url ``` ### URL with user attributes For row-level security, include user attributes in the JWT: ```javascript theme={null} 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}#${token}`; ``` See [User attributes reference](/workspace-admin/user-attributes) 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 ``` https://your-instance.lightdash.cloud/embed/{projectUuid}?theme=dark&backgroundColor=1c1c1c&timezone=America%2FLos_Angeles#{jwtToken} ``` ### `theme` Sets the color scheme for the embedded content. | Value | Description | | ------- | ---------------------------- | | `light` | Light color scheme (default) | | `dark` | Dark color scheme | **Example:** ``` https://app.lightdash.cloud/embed/abc-123?theme=dark#eyJhbGci... ``` ### `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. | Format | Example | Description | | ----------- | ----------------------------- | ------------------------ | | 6-digit hex | `backgroundColor=121212` | Full hex color | | 3-digit hex | `backgroundColor=FFF` | Shorthand hex | | 4-digit hex | `backgroundColor=F008` | Shorthand hex with alpha | | 8-digit hex | `backgroundColor=FF000080` | Hex with alpha | | Transparent | `backgroundColor=transparent` | Show the host background | 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:** ``` https://app.lightdash.cloud/embed/abc-123?backgroundColor=1c1c1c#eyJhbGci... ``` ### `timezone` Beta 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. [What Beta means](/support/feature-maturity-levels). 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. | Format | Example | Description | | ------------------ | ------------------------------ | --------------------------------------- | | IANA timezone name | `timezone=America/Los_Angeles` | Renders the session in Los Angeles time | | IANA timezone name | `timezone=Europe/Berlin` | Renders the session in Berlin time | **Example:** ``` https://app.lightdash.cloud/embed/abc-123?timezone=America%2FLos_Angeles#eyJhbGci... ``` The embed `timezone` param sits at the top of the [timezone resolution priority](/personal-settings/timezone#report-timezone-resolution) for that session and overrides any per-chart timezone pin. It only applies to direct iframe and shareable URL embeds, 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: ``` https://app.lightdash.cloud/embed/abc-123?theme=dark&backgroundColor=1c1c1c&timezone=America%2FLos_Angeles#eyJhbGci... ``` 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 ```javascript Node.js theme={null} 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'; const token = jwt.sign({ content: { type: 'dashboard', dashboardUuid: dashboardUuid, }, }, LIGHTDASH_EMBED_SECRET, { expiresIn: '1h' }); // Build embed URL with theming parameters const embedUrl = `${instanceUrl}/embed/${projectUuid}?theme=dark&backgroundColor=1c1c1c#${token}`; ``` ```python Python theme={null} import jwt import datetime LIGHTDASH_EMBED_SECRET = os.getenv('LIGHTDASH_EMBED_SECRET') instance_url = 'https://app.lightdash.cloud' project_uuid = 'your-project-uuid' dashboard_uuid = 'your-dashboard-uuid' payload = { 'content': { 'type': 'dashboard', 'dashboardUuid': dashboard_uuid, }, 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1) } token = jwt.encode(payload, LIGHTDASH_EMBED_SECRET, algorithm='HS256') # Build embed URL with theming parameters embed_url = f"{instance_url}/embed/{project_uuid}?theme=dark&backgroundColor=1c1c1c#{token}" ``` ## Embedding in HTML ### Basic iframe The simplest way to embed is with a standard HTML iframe: ```html theme={null} ``` ### Recommended attributes ```html theme={null} ``` **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)** ```html theme={null}
``` **Method 2: Modern CSS aspect-ratio (16:9)** ```html theme={null} ``` **Method 3: Fixed viewport percentage** ```html theme={null} ``` ### 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: ```html theme={null} ``` ### Security considerations iframes provide natural security isolation, but you can add additional restrictions: ```html theme={null} ``` **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)** ```javascript theme={null} 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}#${token}`; res.render('dashboard', { embedUrl }); }); ``` **Template (EJS)** ```html theme={null}
``` ### Single-page apps (SPA) Generate URLs via API when component mounts: **React example** ```jsx theme={null} 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
Loading...
; return ( '; } add_shortcode('lightdash', 'lightdash_embed_shortcode'); ``` ## 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: ```javascript theme={null} 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: ```javascript theme={null} 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: ```javascript theme={null} 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}#${token}`; // Redirect to actual embed URL res.redirect(embedUrl); }); ``` Then use: ```html theme={null} ``` ## 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](/embed/reference#jwt-structure) * 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: ```html theme={null} ``` ### 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` # Embedding with React SDK Source: https://docs.lightdash.com/embed/react-sdk Components, props, and hooks for embedding Lightdash content in a React or Next.js app **Try it and see the code.** [embed.lightdash.com](https://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](https://github.com/lightdash/example-embed-dashboard-with-nodejs-app) — a Node.js app that mints tokens server-side and renders a Lightdash dashboard. ## Overview The Lightdash React SDK (`@lightdash/sdk`) provides React components for embedding Lightdash content in your React or Next.js applications. The SDK offers advantages over [iframe embedding](/embed/iframe): * Seamless integration with your React application * Programmatic filters for dashboards * Callbacks for user interactions (e.g., explore navigation) * Custom styling to match your application * TypeScript support with full type definitions For iframe embedding, see the [embedding reference](/embed/reference). ## Set up CORS To use the React SDK, you need to update your "Cross-Origin Resource Sharing" (CORS) policy so the domain hosting your React app is allowed to call the Lightdash API. In Lightdash, go to **Project settings -> Embed configuration -> CORS** and add each origin where you'll use the SDK. CORS settings panel showing regex and exact origin entries Use **origin mode** for exact origins and simple subdomain wildcards: * `https://app.example.com` allows only that exact origin. * `*.example.com` allows HTTPS subdomains like `https://app.example.com` and is saved as a regex pattern. Use **regex mode** (`.*`) for advanced patterns. Enter the pattern body only; Lightdash matches the whole origin automatically. For example, `https:\\/\\/.*\\.example\\.com` allows subdomains of `example.com`. Only add origins you control. Avoid broad patterns that could match arbitrary external domains. For self-hosted deployments, you can also configure instance-level allowed origins with environment variables: ```bash theme={null} LIGHTDASH_CORS_ALLOWED_DOMAINS=https://domain-where-you-are-going-to-use-the-sdk.com ``` CORS is enabled by default. Set `LIGHTDASH_CORS_ENABLED=false` only if you want to disable CORS for the whole instance. Browsers enforce a Same-Origin Policy that blocks a web application from making requests to a domain other than the one that served it. Because the React SDK calls the Lightdash API from your frontend, your instance has to name your application's origin in its CORS configuration for those requests to go through. CORS is **only required for the React SDK**. iframe embedding does not require CORS configuration. ## Installing the Lightdash SDK In your frontend project, use your preferred package manager to install the SDK. ```bash theme={null} npm install @lightdash/sdk # or pnpm add @lightdash/sdk # or yarn add @lightdash/sdk ``` Starting with Lightdash `v2.207.0`, the Embed SDK (`@lightdash/sdk`) requires **React and React DOM 19.2 or later**. Before installing or upgrading the SDK, update both `react` and `react-dom` in your frontend project to meet this requirement. This requirement does not apply to `@lightdash/query-sdk`, which is unaffected. For Next.js, version 15 or later is required. ### Import CSS styles The Lightdash SDK requires CSS styles to render components correctly. Import the SDK's CSS file as the **first import** in your React application's entry point: ```tsx theme={null} import "@lightdash/sdk/sdk.css"; import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; ReactDOM.createRoot(document.getElementById("root")!).render( ); ``` The CSS import must be the first import in your entry file to ensure Lightdash styles load before other styles and avoid conflicts. ## Components and hooks The Lightdash SDK exports components for embedding Lightdash content and hooks for building custom host-app UI around embedded content: * `Lightdash.Dashboard` - Embed complete dashboards with multiple tiles * `Lightdash.DashboardBuilder` - Let embedded users create a brand-new dashboard * `Lightdash.Chart` - Embed individual saved charts * `Lightdash.Explore` - Embed interactive data exploration interface * `Lightdash.AiAgent` - Embed an AI agent so users can chat with their data * `Lightdash.MetricsCatalog` - Embed the project metrics catalog so users can browse and explore metrics * `Lightdash.useLightdashContent` - List spaces, dashboards, charts, and data apps for a custom content catalog * `Lightdash.useLightdashAiAgentThreads` - List an embed user's previous AI agent threads to build a thread history UI All components share common props for authentication and styling. ### Lightdash.Dashboard Embed complete Lightdash dashboards with multiple visualizations, filters, and interactive features. See [Embedding dashboards](/embed/embed-dashboards) for the JWT claims that control what viewers can do. #### Props ```typescript theme={null} type DashboardProps = { // Required instanceUrl: string; // Your Lightdash instance URL token: string | Promise; // JWT (can be async; can be replaced at runtime) // Optional theme?: 'light' | 'dark'; // Force light or dark color scheme styles?: { backgroundColor?: string; // Background color or 'transparent' fontFamily?: string; // Font family for all text }; filters?: SdkFilter[]; // Apply filters programmatically paletteUuid?: string; // Color palette UUID for custom theming contentOverrides?: LanguageMap; // Translate your content (names, titles, markdown) uiOverrides?: SdkUiOverrides; // Translate Lightdash UI strings (filters, menus, buttons) isEditMode?: boolean; // Render the dashboard in edit mode (requires writeActions JWT) onEditModeChange?: ( isEditMode: boolean, ) => void; // Callback when the embed enters or leaves edit mode onExplore?: (options: { chart: SavedChart }) => void; // Callback when user navigates to explore }; ``` #### Basic usage ```tsx theme={null} import Lightdash from '@lightdash/sdk'; function MyDashboard() { return ( ); } ``` #### With filters Apply filters programmatically using the `filters` prop: ```tsx theme={null} import Lightdash, { FilterOperator } from '@lightdash/sdk'; ``` See [Filtering data](#filtering-data) for complete filter documentation. #### With styling ```tsx theme={null} ``` #### With explore callback Track when users navigate to explore: ```tsx theme={null} { console.log('User exploring chart:', chart.name); // Track analytics, show help guides, etc. }} /> ``` #### With edit mode When the JWT includes a `writeActions` claim, you can render an existing dashboard in edit mode and let users rename it, add saved charts from the allowed space, move or resize tiles, and save changes. The host app controls the edit-mode state through `isEditMode` and `onEditModeChange`. ```tsx theme={null} import Lightdash from '@lightdash/sdk'; import { useState } from 'react'; function EditableDashboard() { const [isEditMode, setIsEditMode] = useState(false); return ( <> {!isEditMode && ( )} ); } ``` Add-tile content is filtered to the JWT `writeActions.spaceUuid`, so users can only pick saved charts from the allowed space. See [Write actions](/embed/reference#write-actions) for the JWT claim. ### Lightdash.DashboardBuilder Let embedded users build a brand-new dashboard from scratch. On mount, the SDK creates an empty dashboard in the JWT `writeActions.spaceUuid` and renders it through the same embedded dashboard component as `Lightdash.Dashboard`. The host app controls when the dashboard is in edit mode. Use this when you want your customers to author their own dashboards inside your app — for example, a "Create dashboard" page in your customer portal — without giving them a Lightdash login. #### Props ```typescript theme={null} type DashboardBuilderProps = { // Required instanceUrl: string; // Your Lightdash instance URL token: string | Promise; // JWT with writeActions claim // Optional theme?: 'light' | 'dark'; styles?: { backgroundColor?: string; fontFamily?: string; }; filters?: SdkFilter[]; paletteUuid?: string; contentOverrides?: LanguageMap; uiOverrides?: SdkUiOverrides; isEditMode?: boolean; // Render the new dashboard in edit mode onEditModeChange?: ( isEditMode: boolean, ) => void; // Callback when the embed enters or leaves edit mode onDashboardReady?: ( dashboard: EmbedDashboard, ) => void; // Called once the empty dashboard has been created onExplore?: (options: { chart: SavedChart }) => void; }; ``` #### Basic usage ```tsx theme={null} import Lightdash from '@lightdash/sdk'; import { useEffect, useState } from 'react'; function MyDashboardBuilder() { const [isEditMode, setIsEditMode] = useState(false); const [isReady, setIsReady] = useState(false); return ( <> {isReady && !isEditMode && ( )} setIsReady(true)} /> ); } ``` #### Requirements and behavior * The JWT must include a `writeActions` claim with `spaceUuid`. JWTs without `writeActions` fail closed for write-capable paths. * The new dashboard is created in `writeActions.spaceUuid`, named "Untitled dashboard", and is empty. * Add-tile content (saved charts and SQL charts) is filtered to the same space. * Dashboards created or edited through the SDK are normal Lightdash dashboards — they can be viewed and edited from Lightdash and vice versa. * See [Write actions](/embed/reference#write-actions) for the JWT claim and how to configure the actor and destination space. ### Lightdash.Chart Embed individual saved charts for focused, single-metric displays with minimal UI. #### Props ```typescript theme={null} type ChartProps = { // Required instanceUrl: string; // Your Lightdash instance URL id: string; // Chart UUID (savedQueryUuid) token: string | Promise; // JWT with type: 'chart' // Optional theme?: 'light' | 'dark'; // Force light or dark color scheme styles?: { backgroundColor?: string; // Background color or 'transparent' fontFamily?: string; // Font family for all text }; contentOverrides?: LanguageMap; // Translate your content (names, titles, markdown) uiOverrides?: SdkUiOverrides; // Translate Lightdash UI strings (filters, menus, buttons) }; ``` Unlike Dashboard, Chart does not support `filters` or `onExplore` props since charts are read-only and cannot navigate to explore. #### Basic usage ```tsx theme={null} import Lightdash from '@lightdash/sdk'; function MyChart() { return ( ); } ``` #### With styling ```tsx theme={null} ``` #### Token generation for charts Charts require a JWT with `type: 'chart'`: ```javascript theme={null} // Backend API endpoint import jwt from 'jsonwebtoken'; export function generateChartToken(chartId) { return jwt.sign({ content: { type: 'chart', contentId: chartId, // savedQueryUuid canExportCsv: true, canExportImages: false, canViewUnderlyingData: true, }, }, process.env.LIGHTDASH_EMBED_SECRET, { expiresIn: '24h' }); } ``` See [Embedding charts guide](/embed/embed-charts) for details. ### Lightdash.Explore Embed interactive data exploration interface with full query builder capabilities. #### Props ```typescript theme={null} type ExploreProps = { // Required instanceUrl: string; // Your Lightdash instance URL token: string | Promise; // JWT with canExplore: true // Optional theme?: 'light' | 'dark'; // Force light or dark color scheme styles?: { backgroundColor?: string; // Background color or 'transparent' fontFamily?: string; // Font family for all text }; contentOverrides?: LanguageMap; // Translate your content (names, titles, markdown) uiOverrides?: SdkUiOverrides; // Translate Lightdash UI strings (filters, menus, buttons) }; ``` #### Basic usage ```tsx theme={null} import Lightdash from '@lightdash/sdk'; function MyExplore() { return ( ); } ``` #### With styling ```tsx theme={null} ``` #### Token generation for explores Explores require `canExplore: true` in the JWT: ```javascript theme={null} // Backend API endpoint import jwt from 'jsonwebtoken'; export function generateExploreToken() { return jwt.sign({ content: { type: 'dashboard', // Can use dashboard type dashboardUuid: 'starting-dashboard-uuid', canExplore: true, // Required for explore access canExportCsv: true, canExportImages: true, }, }, process.env.LIGHTDASH_EMBED_SECRET, { expiresIn: '4h' }); } ``` ### Lightdash.AiAgent Embed a Lightdash [AI agent](/agents) so embedded users can chat with their data, generate charts, and save results back to a fixed space — without a Lightdash login. The component renders the agent inside an iframe. Use `threadUuid` to deep-link into an existing thread, or omit it to land on the new-thread screen. #### Props ```typescript theme={null} type AiAgentProps = { // Required instanceUrl: string; // Your Lightdash instance URL agentUuid: string; // Agent to embed (must match the JWT) token: string | Promise; // JWT with content.type: 'aiAgent' // Optional threadUuid?: string; // Open a specific thread on load onThreadChange?: (options: { threadUuid: string }) => void; // Fires when the embed opens or creates a thread theme?: 'light' | 'dark'; styles?: { backgroundColor?: string; }; }; ``` `Lightdash.AiAgent` does not accept `filters`, `contentOverrides`, `uiOverrides`, or `onExplore`. Threads, navigation, and chart actions are managed inside the embedded agent UI. Changing `token` reloads the iframe; see [Rotating tokens](#rotating-tokens). `onThreadChange` fires whenever the embedded agent creates a new thread or opens an existing one. Use it together with `threadUuid` to persist the current conversation in your app (for example in `localStorage` or your own backend) and resume it the next time the user returns. Under the hood, the SDK passes a `targetOrigin` query parameter to the iframe and listens for `lightdash:aiAgentThreadChanged` `postMessage` events from the embedded page — no extra setup is required on your side. #### Basic usage ```tsx theme={null} import Lightdash from '@lightdash/sdk'; function MyAiAgent() { return ( ); } ``` #### Open a specific thread Pass `threadUuid` to deep-link the embed into a specific conversation on mount: ```tsx theme={null} ``` #### Persist and resume the last conversation Combine `threadUuid` and `onThreadChange` to keep users on their most recent thread across page reloads. This example stores the latest thread UUID in `localStorage`: ```tsx theme={null} import Lightdash from '@lightdash/sdk'; import { useState } from 'react'; const STORAGE_KEY = 'acme-shop:lightdash-ai-thread'; function ShopInsightsAgent({ token }: { token: string }) { const [threadUuid, setThreadUuid] = useState( () => localStorage.getItem(STORAGE_KEY) ?? undefined, ); return ( { setThreadUuid(nextThreadUuid); localStorage.setItem(STORAGE_KEY, nextThreadUuid); }} /> ); } ``` #### Token generation for AI agents AI agent embeds require a JWT with `content.type: 'aiAgent'` and a `writeActions` claim that pins the destination space and the actor used for agent queries and chart saves: ```javascript theme={null} // Backend API endpoint import jwt from 'jsonwebtoken'; export function generateAiAgentToken() { return jwt.sign({ content: { type: 'aiAgent', projectUuid: 'your-project-uuid', agentUuid: 'your-agent-uuid', }, writeActions: { serviceAccountUserUuid: 'service-account-user-uuid', spaceUuid: 'destination-space-uuid', }, userAttributes: { tenant_id: 'tenant-abc', }, }, process.env.LIGHTDASH_EMBED_SECRET, { expiresIn: '1h' }); } ``` See [Embedding AI agents](/embed/embed-ai-agents) for the full guide and [AI agent token](/embed/reference#ai-agent-token) for the complete JWT structure. ### Lightdash.MetricsCatalog Embed the Lightdash [metrics catalog](/semantic-layer/metrics) so embedded users can browse the metrics defined in a project, preview them, and — when the JWT allows it — continue into Explore without leaving your app. When a viewer clicks **Explore from here** on a metric, the SDK swaps in an embedded Explore view; a **Back** action returns them to the catalog. #### Props ```typescript theme={null} type MetricsCatalogProps = { // Required instanceUrl: string; // Your Lightdash instance URL token: string | Promise; // JWT with content.type: 'metricsCatalog' // Optional theme?: 'light' | 'dark'; // Force light or dark color scheme styles?: { backgroundColor?: string; // Background color or 'transparent' fontFamily?: string; // Font family for all text }; }; ``` `Lightdash.MetricsCatalog` does not accept `filters`, `contentOverrides`, `uiOverrides`, or `onExplore`. The catalog and the embedded Explore it launches are managed inside the component. #### Basic usage ```tsx theme={null} import Lightdash from '@lightdash/sdk'; function MyMetricsCatalog() { return ( ); } ``` #### Token generation for the metrics catalog Metrics catalog embeds require a JWT with `content.type: 'metricsCatalog'` and a `projectUuid`. Set `content.canExplore` to `true` to let embedded users open Explore from a metric, and include a `writeActions` claim if you want them to save the resulting charts back to Lightdash. ```javascript theme={null} // Backend API endpoint import jwt from 'jsonwebtoken'; export function generateMetricsCatalogToken() { return jwt.sign({ content: { type: 'metricsCatalog', projectUuid: 'your-project-uuid', canExplore: true, }, writeActions: { serviceAccountUserUuid: 'service-account-user-uuid', spaceUuid: 'destination-space-uuid', }, userAttributes: { tenant_id: 'tenant-abc', }, }, process.env.LIGHTDASH_EMBED_SECRET, { expiresIn: '1h' }); } ``` Omit `canExplore` (or set it to `false`) to publish a read-only browse experience. See [Embedding the metrics catalog](/embed/embed-metrics-catalog) for the full guide and [Metrics catalog token](/embed/reference#metrics-catalog-token) for the complete JWT structure. ## API hooks ### Lightdash.useLightdashContent Use `useLightdashContent` when you want your own app to list Lightdash content instead of embedding the Lightdash home page. A common pattern is to let customers choose a space in your UI, show the dashboards and charts in that space, then render the selected object with `Lightdash.Dashboard` or `Lightdash.Chart`. The hook calls the Lightdash content API and returns metadata only. It does not render the selected chart or dashboard, and it does not replace the chart or dashboard embed token you pass to the render component. #### Backend: generate an API access token Generate the token on your backend with your Lightdash embed secret. Never expose the embed secret in browser code. ```typescript theme={null} import jwt from 'jsonwebtoken'; export function generateContentCatalogToken() { return jwt.sign( { content: { type: 'apiAccess', projectUuid: 'your-project-uuid', serviceAccountUserUuid: 'service-account-user-uuid', }, user: { externalId: 'customer-user-123', email: 'customer@example.com', }, userAttributes: { tenant_id: 'tenant-abc', }, }, process.env.LIGHTDASH_EMBED_SECRET, { expiresIn: '1h' }, ); } ``` The service account controls what the hook can list. If the service account cannot view a private space, content from that space is not returned. #### Frontend: list content in a space ```tsx theme={null} import Lightdash from '@lightdash/sdk'; function ContentCatalog({ instanceUrl, projectUuid, token, spaceUuid, }: { instanceUrl: string; projectUuid: string; token: string; spaceUuid: string; }) { const { data, error, isLoading, refetch } = Lightdash.useLightdashContent( { instanceUrl, projectUuid, auth: { type: 'embedToken', token, }, }, { spaceUuids: [spaceUuid], contentTypes: ['dashboard', 'chart'], page: 1, pageSize: 50, sortBy: 'name', sortDirection: 'asc', }, ); if (isLoading) return

Loading content...

; if (error) return

Unable to load content

; return (
{data?.data.map((item) => ( ))}
); } ``` #### Options ```typescript theme={null} type ListContentOptions = { projectUuids?: string[]; spaceUuids?: string[]; parentSpaceUuid?: string; contentTypes?: Array<'space' | 'dashboard' | 'chart' | 'data_app'>; page?: number; pageSize?: number; search?: string; sortBy?: 'name' | 'space_name' | 'last_updated_at'; sortDirection?: 'asc' | 'desc'; }; ``` Use the `spaceUuids` option as a filter, not as an authorization boundary — authorization comes from the API access token's service account permissions. `apiAccess` tokens are for API reads such as content listing; to let embedded users save charts or dashboards, use an embed token that supports `writeActions`. ### Lightdash.useLightdashAiAgentThreads Use `useLightdashAiAgentThreads` when you want to show your users a list of their previous AI agent conversations — for example a "Recent chats" sidebar next to a `Lightdash.AiAgent` embed. The hook calls the AI agent threads endpoint with the embed JWT, so it returns only threads that belong to the JWT-authenticated embed user and are scoped to their embed space. Pair it with `Lightdash.AiAgent`'s `threadUuid` and `onThreadChange` props to let users resume any past conversation. #### Options ```typescript theme={null} type ListAiAgentThreadsOptions = { agentUuid: string; // The agent whose threads should be listed projectUuid?: string; // Falls back to the projectUuid on LightdashApiClientConfig }; ``` The hook takes the same `LightdashApiClientConfig` as `useLightdashContent`, with `auth.type: 'embedToken'` and the AI agent embed JWT as the token. #### Types ```typescript theme={null} type LightdashAiAgentThreadResults = LightdashAiAgentThread[]; // One entry per thread the embed user can see. Full shape lives in // @lightdash/common's ApiAiAgentThreadSummaryListResponse; the useful fields // for building thread history UIs are: type LightdashAiAgentThread = { uuid: string; title?: string; firstMessage: { message: string }; // ...additional metadata such as timestamps }; ``` #### Example: thread history + resume ```tsx theme={null} import Lightdash, { useLightdashAiAgentThreads, type LightdashApiClientConfig, } from '@lightdash/sdk'; import { useState } from 'react'; const STORAGE_KEY = 'acme-shop:lightdash-ai-thread'; function ShopInsightsAgentWithHistory({ token, projectUuid, agentUuid, }: { token: string; projectUuid: string; agentUuid: string; }) { const apiConfig: LightdashApiClientConfig = { instanceUrl: 'https://app.lightdash.cloud', projectUuid, auth: { type: 'embedToken', token }, }; const threads = Lightdash.useLightdashAiAgentThreads(apiConfig, { agentUuid, projectUuid, }); const [threadUuid, setThreadUuid] = useState( () => localStorage.getItem(STORAGE_KEY) ?? undefined, ); return (
{ setThreadUuid(nextThreadUuid); localStorage.setItem(STORAGE_KEY, nextThreadUuid); // Refresh the sidebar so new threads show up immediately. threads.refetch(); }} />
); } ``` `useLightdashAiAgentThreads` uses the same embed JWT you pass to `Lightdash.AiAgent`. The token's `content.agentUuid` and `writeActions.spaceUuid` are what scope the returned threads — the hook cannot list threads from a different agent or space, even if you pass a different `agentUuid` argument. ## Generating embed tokens All SDK components require JWTs generated server-side, signed with the embed secret from [embed setup](/embed/set-up-embedding). Here's a complete example, including [user attributes](/workspace-admin/user-attributes) for row-level filtering: ### Backend API endpoint ```typescript theme={null} // server/api/embed-token.ts import jwt from 'jsonwebtoken'; export async function generateEmbedToken(req, res) { // Authenticate user const userId = req.user.id; const user = await getUserFromDatabase(userId); // Generate token with user-specific attributes const token = jwt.sign({ content: { type: 'dashboard', dashboardUuid: 'your-dashboard-uuid', dashboardFiltersInteractivity: { enabled: 'all', }, canExportCsv: true, canExplore: true, }, userAttributes: { tenant_id: user.tenantId, // Row-level filtering }, user: { externalId: user.id, email: user.email, }, }, process.env.LIGHTDASH_EMBED_SECRET, { expiresIn: '1h' }); res.json({ token }); } ``` ### Frontend React component ```tsx theme={null} import Lightdash from '@lightdash/sdk'; import { useState, useEffect } from 'react'; function EmbeddedDashboard() { const [token, setToken] = useState(null); useEffect(() => { // Fetch token from your backend fetch('/api/embed-token') .then(res => res.json()) .then(data => setToken(data.token)); }, []); if (!token) return
Loading...
; return ( ); } ``` To ensure security, JWT generation code must run **in your backend**, and the **Lightdash embed secret** must never be exposed in frontend code. This prevents unauthorized access and protects sensitive data. ### Rotating tokens Self-hosted instances need Lightdash `2.137.0` or later to replace a token at runtime. On earlier versions the component keeps sending the first token it was given. Short-lived tokens are the safer default. To keep a long-lived embed working, mint a new token before the current one expires and pass it to the same component. The `token` prop can change at any time: the component stays mounted, dashboard filters, Explore state and chart edit mode are kept, and every request from then on carries the new token. Do not change the component's React `key` to swap tokens; that remounts it and resets its state. ```tsx theme={null} import Lightdash from '@lightdash/sdk'; import { useState, useEffect } from 'react'; function EmbeddedDashboard() { const [token, setToken] = useState(null); useEffect(() => { let timer: ReturnType; const refresh = async () => { // Your backend returns the JWT and how long it is valid for const { token, expiresInSeconds } = await fetch('/api/embed-token') .then(res => res.json()); setToken(token); // Mint the next token a minute before this one expires timer = setTimeout(refresh, (expiresInSeconds - 60) * 1000); }; void refresh(); return () => clearTimeout(timer); }, []); if (!token) return
Loading...
; return ( ); } ``` This applies to `Lightdash.Dashboard`, `Lightdash.DashboardBuilder`, `Lightdash.Chart`, `Lightdash.Explore`, and `Lightdash.MetricsCatalog`. * Refresh before expiry. A request that already failed on an expired token is not retried with the new one. * The new token replaces the old one for every SDK component on the page. Render one SDK component per page; two components with different tokens are not supported. * `Lightdash.AiAgent` renders an iframe and passes the token in its URL, so changing the token reloads the agent and closes the open thread. Mint AI agent tokens that outlive the session instead. ## Applying styles Override styles within Lightdash components to match your application's design. ### Supported style overrides ```typescript theme={null} styles?: { fontFamily?: string; // Sets all fonts within the component backgroundColor?: string; // Sets the background color or 'transparent' } ``` Both properties accept normal CSS values and are set on a `styles` object passed to any component. ### Font family Sets the font family for all text within the embedded content. Font sizes and other properties are preserved. ```typescript theme={null} ``` Some charts and components set `font-family` explicitly, so the `fontFamily` style is applied with higher specificity to override these. ### Background color Sets the background for the embedded content. Can be any color value or `'transparent'`. ```typescript theme={null} ``` ### Complete example ```typescript theme={null} ``` ### CSS class overrides Beyond the `styles` prop, you can target embedded dashboard elements directly from your application's stylesheet. Each element below carries a stable, human-readable classname that is part of the SDK's public API — it won't change when internal layout does, so your overrides stay resilient across releases. | Class | Element | | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ld-dashboard-header` | The dashboard header bar | | `ld-dashboard-filters` | The filter bar row | | `ld-dashboard-filter` | An individual filter pill | | `ld-dashboard-date-zoom` | The date-zoom control(s) | | `ld-dashboard-parameters` | The parameters row | | `ld-dashboard-parameter` | An individual parameter pill | | `ld-dashboard-filter-dropdown` | An open filter's dropdown | | `ld-dashboard-date-zoom-dropdown` | The open date-zoom menu | | `ld-dashboard-parameter-dropdown` | An open parameter's dropdown | | `ld-dashboard-guided-setup` | The guided setup card shown while [required filters or requirement groups](/explore/dashboards/filter#required-filters-and-filter-requirement-groups) are unmet | ```css theme={null} .ld-dashboard-filters { gap: 1rem; } .ld-dashboard-filter-dropdown { font-size: 1rem; min-width: 380px; } ``` The filter, date-zoom, and parameter dropdowns render in a portal at the page root — outside the dashboard container — so target them with a global selector rather than as a descendant of the embedded dashboard. ## Light and dark mode Use the `theme` prop to render embedded content in either `'light'` or `'dark'` mode. This is typically driven by the host application's own theme state, so the embedded dashboard, chart, or explore matches the surrounding UI. ```tsx theme={null} ``` The `theme` prop is supported on `Lightdash.Dashboard`, `Lightdash.Chart`, and `Lightdash.Explore`. When `theme` is set, the SDK forces the Mantine color scheme and ignores any user-toggled preference stored in the embed. Omit the prop to let the embed use its default (light) color scheme. ### Syncing with your app's theme Pass your app's current theme value directly to the SDK so the embed re-renders when it changes: ```tsx theme={null} import Lightdash from '@lightdash/sdk'; import { useState } from 'react'; function EmbeddedDashboard() { const [theme, setTheme] = useState<'light' | 'dark'>('light'); return ( ); } ``` ### Combining with `styles.backgroundColor` When `theme` is set, the embed uses the matching Mantine body background by default. If you also pass `styles.backgroundColor`, your value takes precedence: ```tsx theme={null} ``` ## Color palettes You can customize the appearance of embedded dashboards using color palettes. Define multiple color palettes in your organization settings, then apply them to embedded dashboards using the `paletteUuid` prop. For more on customizing appearance, see [customizing the appearance of your project](/workspace-admin/appearance). ### Setting up color palettes 1. Go to **Organization settings > Appearance** in Lightdash 2. Define one or more color palettes 3. Copy the palette UUID for the palette you want to use (or fetch from API `GET /api/v1/org/color-palettes`) ### Applying a palette Pass the `paletteUuid` prop to the `Lightdash.Dashboard` component: ```tsx theme={null} ``` ## Filtering data Filters can be passed to `` to filter dimensions by values. Filters are applied as AND operations, each further restricting results. The Chart and Explore components do not support the `filters` prop. For the `filters` prop to work, your JWT must have `dashboardFiltersInteractivity` set to `enabled: 'all'`. Without this configuration, filters will not be applied. ### Filter structure ```typescript theme={null} type SdkFilter = { model: string; // The model the dimension is part of field: string; // The name of the dimension to filter by operator: FilterOperator; // The filter operator (enum) value: unknown | unknown[]; // The value(s) to filter against }; ``` ### Basic example ```javascript theme={null} ``` ### Multiple filters Filters are applied as AND operations: ```javascript theme={null} ``` ### FilterOperator enum Import `FilterOperator` from the SDK: ```typescript theme={null} import Lightdash, { FilterOperator } from '@lightdash/sdk'; ``` Available operators: | Operator | Description | Value Type | | -------------------------------------- | ----------------------------- | ------------------- | | `FilterOperator.IS_NULL` | Field is null | n/a | | `FilterOperator.NOT_NULL` | Field is not null | n/a | | `FilterOperator.EQUALS` | Field equals value | single value | | `FilterOperator.NOT_EQUALS` | Field does not equal value | single value | | `FilterOperator.STARTS_WITH` | Field starts with value | single value | | `FilterOperator.ENDS_WITH` | Field ends with value | single value | | `FilterOperator.INCLUDE` | Field includes any of values | array | | `FilterOperator.NOT_INCLUDE` | Field does not include values | array | | `FilterOperator.LESS_THAN` | Field is less than value | single value | | `FilterOperator.LESS_THAN_OR_EQUAL` | Field is ≤ value | single value | | `FilterOperator.GREATER_THAN` | Field is greater than value | single value | | `FilterOperator.GREATER_THAN_OR_EQUAL` | Field is ≥ value | single value | | `FilterOperator.IN_THE_PAST` | Date in the past N units | single value | | `FilterOperator.NOT_IN_THE_PAST` | Date not in past N units | single value | | `FilterOperator.IN_THE_NEXT` | Date in the next N units | single value | | `FilterOperator.IN_THE_CURRENT` | Date in current period | single value | | `FilterOperator.NOT_IN_THE_CURRENT` | Date not in current period | single value | | `FilterOperator.IN_BETWEEN` | Field between two values | array with 2 values | | `FilterOperator.NOT_IN_BETWEEN` | Field not between values | array with 2 values | ### Available fields Only fields that are available for filtering can be filtered. These are specified in the JWT passed to the SDK. To generate tokens with filterable fields, configure your embed in the Lightdash UI or include the appropriate fields in your JWT structure. ## Localization The React SDK has two translation props, split by what they translate: | Prop | Translates | Shape | | ------------------ | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | `contentOverrides` | **Your content**: dashboard and chart names, tile titles, markdown, custom filter labels | `LanguageMap`, slug-keyed, generated with `lightdash download --language-map` | | `uiOverrides` | **Lightdash's UI**: filter operators and inputs, the filter popover, date zoom, tile menus, export buttons | Flat `{ key: string }` map with a fixed, typed key set | There is no locale setting and no bundled language packs. Your app owns locale state and passes the translated strings for the language it wants. Anything you don't override renders in the built-in English. Both props are accepted by `Lightdash.Dashboard`, `Lightdash.DashboardBuilder`, `Lightdash.Chart`, and `Lightdash.Explore`. They are React SDK props only; [iframe embeds](/embed/iframe) are not translatable. ### Translating your content with `contentOverrides` `contentOverrides` translates the content you author in Lightdash: dashboard names and descriptions, tile titles, chart names, axis labels, series names, markdown content, and the custom labels you've set on dashboard filters. Recommended tools: * **Translation maps** – The Lightdash CLI can generate translation maps when downloading content as code * **Runtime translation management** – Use a translation library like `i18next` * **Translation production tools** – Tools like **Locize** help manage translations efficiently #### Video overview