Migrate to the new embed payload
This guide is for teams that already embed dashboards with per-dashboard embed links (the setup documented in Legacy single-dashboard embedding) and want to move to the new embed payload. It walks through each part of a legacy payload and shows where it goes in the new one.
Migrating is optional. Your existing embed links keep working, so you can move when it suits you, one dashboard at a time if you like.
Benefits
The new embed payload works the same way as your legacy embed: your backend signs a JWT, and your frontend loads it in an iframe. Only the payload syntax and the credentials change.
Switching to the new embed payload gets you:
- One embed credential: a single Key ID and Secret for every embed, instead of an embed code and secret key per dashboard. It's easier to set up, and there's only one secret to store and manage.
- Multiple dashboards in one embed: list several dashboards in one payload and let viewers move between them
- Exploration on your datasets: drill-down, break down, view underlying data, and building charts on the fly with self-serve exploration
- User-built dashboards: personal and organization workspaces through Identity & Workspace
- Ask AI: conversational analytics with Ask AI
- Email subscriptions: scheduled reports with email subscriptions
What changes at a glance
Most of your payload stays the same. The main changes are the credentials and how you tell Holistics which dashboards to embed.
- Before: Legacy per-dashboard embed link
- After: New embed payload
- Your backend const jwt = require('jsonwebtoken'); const embedCode = '275XXXXXXXXX'; // per dashboard const secretKey = '382fXXXXXXXX'; // per dashboard const payload = { // dashboard implied by the embed code settings: { dashboard_autorun_on_changes: true }, permissions: { row_based: [ ... ] }, exp: Math.floor(Date.now() / 1000) + 24 * 60 * 60, }; const token = jwt.sign(payload, secretKey); const url = `${host}/embed/${embedCode}?_token=${token}`;
- Your backend const jwt = require('jsonwebtoken'); const embedKeyId = 'abc123XXXXXX'; // one for all const embedSecret = '9f1dXXXXXXXX'; const payload = { objects: { dashboards: ['sales_overview'] }, settings: { dashboard_autorun_on_changes: true }, permissions: { row_based: [ ... ] }, exp: Math.floor(Date.now() / 1000) + 24 * 60 * 60, }; const token = jwt.sign(payload, embedSecret); const url = `${host}/embed/${embedKeyId}?_token=${token}`;
- a-l-cred → a-r-cred (Step 1: new credentials)
- a-l-dash → a-r-dash (Step 2: list dashboards)
- a-l-url → a-r-url (Step 1: sign with the new secret)
Prerequisites
Here's what to have ready before you begin:
- Admin access in Holistics, so you can generate embed credentials.
- Access to your application code. Your backend builds and signs the payload, and your frontend loads the embed, so you'll update both.
- Dashboards as-code. Make sure the dashboards you embed are canvas dashboards defined as code in AML. Only these can move to the new embed payload. Any others stay on their legacy embed links.
- A local or staging environment to test in. We recommend testing the new embed carefully there before you replace the legacy iframe for real users.
Migration
For most embeds, only two things change: the credential you sign with, and how the payload says which dashboard to show.
Step 1: Create and replace your embed credentials
The new embed payload uses one shared credential for every embed, instead of an embed code and secret key per dashboard. Create it once, then swap it into your code.
1. Generate shared credentials. Go to Tools → Embedded Analytics, click Generate embed credentials, and note the two values:
- Key ID: identifies your embeds in the iframe URL
- Secret: signs the payload. Keep it on your backend.
See Quickstart, Step 1 for a walkthrough of this page. Generating shared credentials doesn't change your per-dashboard embed codes or secret keys, so your legacy embed links keep working.
2. Replace them in your code. Swap the old credentials for the new ones on both sides:
-
Backend: sign the payload with the shared Secret instead of the per-dashboard secret key:
// Before: jwt.sign(payload, secretKey, { algorithm: 'HS256' })const token = jwt.sign(payload, embedSecret, { algorithm: 'HS256' }); -
Frontend: point the iframe at your Key ID instead of the embed code, keeping the same regional domain (
secure,us, oreu) or custom domain:<!-- Before: legacy per-dashboard embed link --><iframe src="https://{region_domain}.holistics.io/embed/{embed_code}?_token={token}"></iframe><!-- After: new embed payload --><iframe src="https://{region_domain}.holistics.io/embed/{embed_key_id}?_token={token}"></iframe>
Step 2: List your dashboards in objects.dashboards
In the legacy setup, the embed code in the iframe URL decides which dashboard loads. In the new embed payload, the payload says which dashboards to show, by their unique name in AML (the name after Dashboard), not by numeric ID, embed code, or display title.
const payload = {
objects: {
dashboards: ['sales_overview'], // unique AML name of the dashboard you embedded before
},
// your existing settings and permissions stay as they are
exp: Math.floor(Date.now() / 1000) + 24 * 60 * 60,
};
Your dashboard now loads through the new embed payload. From here, you can unlock more features such as exploration, user-built dashboards, and Ask AI by adding keys to the same payload.
What stays the same
The rest of your payload keeps working as it is, so there's nothing to rewrite:
-
settings: same object, same keys. If yours still setsenable_export_data, you can drop it: it's deprecated. See the Settings reference for everything you can set. -
permissions: row-level rules inpermissions.row_basedkeep the same shape (path,operator,values, optionalmodifier).Dataset row-level permissions now apply tooRow-level permissions defined in your datasets now apply to the embed automatically, combined with
permissions.row_basedusing AND. Consider moving your rules into dataset permissions matched byuser_attributes, or pass__ALL__for an attribute to bypass a dataset rule. -
exp: unchanged. Keep it at 24 hours or less.
Replicate drill-through and default filter values
Two legacy features work a little differently. Skip this section if you don't use them.
Drill-through
Drill-through in an embed now works the same way as drill-through inside Holistics. Instead of a drillthroughs key in the payload, you set it up in two places:
- Set up drill-through on your dashboards in Holistics, exactly as you would for internal users. See Drill-through.
- Add the destination dashboards to
objects.dashboards, by unique name. Viewers can only drill into dashboards that are part of the embed, so this list controls where they can go.
const payload = {
objects: {
dashboards: [
'sales_overview', // starting point
'product_details', // was "123" in drillthroughs
'customer_analysis', // was "456" in drillthroughs
],
},
exp: Math.floor(Date.now() / 1000) + 24 * 60 * 60,
};
Legacy drillthroughs could also override filters on each destination dashboard. That isn't supported, so use the dashboard:filters:apply event after the destination loads instead.
Default filter values
The new embed payload has no filters key, so the default_condition entries from your legacy payload move out of the payload.
For simple conditions, use URL query parameters. Add each filter's unique name to the embed URL with the value to apply. Query parameters support is, equal to, and matches-style conditions, and apply to the dashboard the URL opens to:
https://{region_domain}.holistics.io/embed/{embed_key_id}/objects/sales_overview?_token=...®ion=Europe
For other conditions, use embed events. URL query parameters don't support relative dates (such as "last 6 months") or other custom operators. For those, change the dashboard's filters with the dashboard:filters:apply event once the dashboard has loaded:
iframe.contentWindow.postMessage(
{
eventName: 'dashboard:filters:apply',
payload: {
filters: {
region: { operator: 'is', values: ['Europe'] },
},
},
},
'https://your-holistics-domain.com'
);
See Default filter values for the URL syntax and Sync filters between your app and the dashboard for the event pattern.
URL parameters aren't signed, and viewers could always change legacy default filters in the dashboard anyway. If a legacy filters entry was quietly acting as a data restriction, move it to permissions.row_based or user_attributes instead. See Enforcing data access control the right way.
Before and after
Here's a complete legacy payload next to its new version. It embeds a sales dashboard with one row-level rule, auto-run on, a default region filter, and one drill-through destination. Highlighted lines are the ones that change: everything else stays as it is.
- Before: legacy payload
- After: new embed payload
const jwt = require('jsonwebtoken');
const embedCode = '275XXXXXXXXX'; // per-dashboard embed code
const secretKey = '382fXXXXXXXX'; // per-dashboard secret key
const payload = {
settings: {
dashboard_autorun_on_changes: true,
},
permissions: {
row_based: [
{
path: { dataset: 'ecommerce', model: 'countries', field: 'name' },
operator: 'is',
values: ['Vietnam'],
},
],
},
filters: {
region: {
default_condition: { operator: 'is', values: ['Asia'], modifier: null },
},
},
drillthroughs: {
'123': {}, // product_details
},
exp: Math.floor(Date.now() / 1000) + 24 * 60 * 60,
};
const token = jwt.sign(payload, secretKey, { algorithm: 'HS256' });
const url = `https://secure.holistics.io/embed/${embedCode}?_token=${token}`;
const jwt = require('jsonwebtoken');
const embedKeyId = 'abc123XXXXXX'; // Step 1: shared Key ID
const embedSecret = '9f1dXXXXXXXX'; // Step 1: shared Secret
const payload = {
objects: {
dashboards: ['sales_overview', 'product_details'], // Step 2, plus the drill-through target
},
settings: {
dashboard_autorun_on_changes: true,
},
permissions: {
row_based: [
{
path: { dataset: 'ecommerce', model: 'countries', field: 'name' },
operator: 'is',
values: ['Vietnam'],
},
],
},
exp: Math.floor(Date.now() / 1000) + 24 * 60 * 60,
};
const token = jwt.sign(payload, embedSecret, { algorithm: 'HS256' });
// Key ID replaces the embed code; the default filter moves to the URL
const url = `https://secure.holistics.io/embed/${embedKeyId}/objects/sales_overview?_token=${token}®ion=Asia`;
Recommended rollout plan
Legacy links keep working throughout, so there's no need to switch every customer at once. We recommend rolling out in phases:
- Build and test locally. Migrate one embedded dashboard in your local environment and compare it side by side with the legacy embed. Load it as a few different viewers and confirm each one sees only their own rows.
- Verify in staging. Deploy to staging and test with realistic viewer identities, including any drill-through and default filter behavior.
- Release to a small group. Serve the new iframe to a subset of customers first, for example behind a feature flag, and keep the legacy iframe as a fallback.
- Roll out to everyone. Once the new embed behaves as expected, switch the remaining customers.
- Retire the legacy code. When no viewer uses the old iframe URL, remove the legacy payload code and embed links.
FAQs
Do my existing embed links stop working?
No. Per-dashboard embed links keep working, and the legacy pages stay available so you can maintain them. Nothing forces you to migrate.
Can I migrate one dashboard at a time?
Yes. Each legacy embed link is independent, so you can move one dashboard to the new embed payload while the others keep running on their own embed codes. Once several dashboards are migrated, you can also combine them into a single embed.
Can I reuse my existing tokens?
Not as they are. A new embed expects a payload in the new shape, signed with the shared Secret. Your backend builds new tokens for the new iframe URL, while the old code path keeps serving legacy links until you retire it.
Where do I find a dashboard's unique name?
Open the dashboard's code in Holistics and check the name after Dashboard in its AML definition. For example, Dashboard sales_overview { has the unique name sales_overview.
Do I have to persist my setup as an Embed Portal?
No. Listing objects inline in the payload is enough. If your payloads grow or you want the list of embedded objects under version control, you can persist your setup as code later without changing credentials or the iframe.
Related documentation
- Quickstart: the new setup end to end
- Embed payload reference: every payload and URL parameter
- Row-level permissions: ad hoc rules and dataset rules
- Embed events: control filters and navigation from your app
- Legacy single-dashboard embedding: the setup you're migrating from