Skip to main content

Embed events: Control embedded dashboards with JavaScript

Upcoming Feature

This feature is under development and will be coming soon!

Introduction

Embed events let you control embedded Holistics dashboards from your application using JavaScript. Send commands, listen for user interactions, and keep your app and dashboard in sync, all through the browser's standard postMessage API.

Use cases include:

  • Filter sync: Push filter values from your app into the dashboard, or listen when users change filters inside the iframe and apply those values elsewhere in your app.
  • Dashboard navigation: Build a custom menu (horizontal nav, tab-style switcher) in your app that triggers dashboard changes inside the iframe with a smooth, no-reload experience.
  • Error handling & token refresh: React to errors from the iframe by showing contextual messages to users, notifying admins, or automatically refreshing an expired JWT token to keep the session alive.

How it works

Communication flows in two directions between your application (the host) and the Holistics iframe (embedded analytics):

Embed events communication diagram

Host to iframe: Your app sends commands (e.g., update filters, navigate to a different dashboard) by calling iframe.contentWindow.postMessage().

Iframe to host: The embedded dashboard emits events (e.g., filter changed, navigation completed) via parent.postMessage(), and your app listens with window.addEventListener("message", ...).

Supported events

UNDER DEVELOPMENT

Embed events are currently under design and development. The event names, payloads, and behavior described below may change before general availability.

EventDirectionDescription
embed:loadediframe to hostEmits when the embed is ready. Includes a list of all available dashboards and datasets.
page:changehost to iframeSwitch the iframe to a different dashboard or object without reloading.
page:changediframe to hostEmits after a navigation action completes. Echoes back the current page path.
dashboard:runhost to iframeRefresh the current dashboard.
dashboard:loadediframe to hostEmits once when the dashboard finishes its initial load. Includes metadata and default filter values. dashboard:filters:applied does not emit during this load.
dashboard:filters:applyhost to iframeApply filter values to the current dashboard. Only takes effect after dashboard:loaded has emitted.
dashboard:filters:appliediframe to hostEmits when the user or dashboard:filters:apply changes filter values, after dashboard:loaded.
embed:token:refreshhost to iframePass a new JWT token to extend the embed session.
embed:erroriframe to hostEmits when an error occurs inside the embed.

Host to iframe

Your app sends these commands to control the embedded dashboard:

page:change

Switch the iframe to a different dashboard or object without reloading.

Event structure:

{
eventName: "page:change",
payload: {
path: string,
}
}

Event properties:

path
Path to navigate to. Use /objects/{object_name} for a dashboard or dataset, or /ai for the Ask AI page.

dashboard:filters:apply

Apply filter values to the current dashboard and reload its data without reloading the iframe. This command only takes effect after dashboard:loaded has emitted.

Event structure:

{
eventName: "dashboard:filters:apply",
payload: {
filters: {
[block_name]: {
operator: string,
values: string[],
modifier?: string,
}
}
}
}

Event properties:

filters
Map of filter block names to filter values. Each entry includes the following properties:
  • operator: Filter operator (e.g., "is", "is_in_the_range").
  • values: Array of filter values to apply.
  • modifier: Optional. Filter modifier (e.g., for relative date ranges).

dashboard:run

Refresh the current dashboard.

Event structure:

{
eventName: "dashboard:run",
}

No payload required.


embed:token:refresh

Pass a new JWT token to extend the embed session without reloading the iframe.

Event structure:

{
eventName: "embed:token:refresh",
payload: {
token: string,
}
}

Event properties:

token
The new JWT token to replace the current session token.

Iframe to host

The embedded dashboard emits these events to notify your app:

embed:loaded

Emits when the embed is ready. Provides a list of all dashboards and datasets available in the embed portal, useful for building custom navigation.

Event structure:

{
eventName: "embed:loaded",
payload: {
objects: {
dashboards: [
{
uname: string,
id: string,
title: string,
path: string,
workspace?: 'org' | 'personal',
}
],
datasets: [
{
uname: string,
id: string,
title: string,
}
],
}
}
}

Event properties:

objects.dashboards
List of dashboards available in the embed. Each item includes the following properties:
  • uname: Unique identifier for the dashboard.
  • id: Dashboard ID.
  • title: Display title.
  • path: URL path to the dashboard.
  • workspace: Optional. 'org' if the dashboard belongs to the organization workspace, 'personal' if it belongs to a personal workspace.
objects.datasets
List of datasets available in the embed. Each item includes the following properties:
  • uname: Unique identifier for the dataset.
  • id: Dataset ID.
  • title: Display title.

dashboard:loaded

Emits once when the dashboard finishes its initial load. At this point, dashboard metadata and default filter values are available to the host app. Note: dashboard:filters:applied only emits for filter changes made after this event. The initial filter values captured here do not trigger it.

Event structure:

{
eventName: "dashboard:loaded",
payload: {
dashboard: {
uname: string,
id: string,
title: string,
path: string,
workspace?: 'org' | 'personal',
},
filters: {
[block_name]: {
operator: string,
value: unknown,
modifier?: string,
}
}
}
}

Event properties:

dashboard
Metadata about the loaded dashboard:
  • uname: Unique identifier for the dashboard.
  • id: Dashboard ID.
  • title: Display title.
  • path: URL path to the dashboard.
  • workspace: Optional. 'org' or 'personal'.
filters
The dashboard's current filter values at load time. Each entry includes the following properties:
  • operator: Filter operator.
  • value: Current filter value.
  • modifier: Optional. Filter modifier.

page:changed

Emits after a navigation action completes. Echoes back the current page path so your app can stay in sync.

Event structure:

{
eventName: "page:changed",
payload: {
path: string,
}
}

Event properties:

path
The current page path after navigation (e.g., /objects/{object_uname} or /ai).

dashboard:filters:applied

Emits whenever filter values change on the dashboard, either by the user or programmatically via dashboard:filters:apply. This event is only active after dashboard:loaded has emitted; filter changes during the initial load (including default filter values) do not trigger it. Only the changed filters are included in the payload.

Event structure:

{
eventName: "dashboard:filters:applied",
payload: {
dashboard: {
uname: string,
id: string,
title: string,
type: string,
},
filters: {
[block_name]: {
operator: string,
value: unknown,
modifier?: string,
}
}
}
}

Event properties:

dashboard
Metadata about the dashboard where filters were applied:
  • uname: Unique identifier for the dashboard.
  • id: Dashboard ID.
  • title: Display title.
  • type: Dashboard type.
filters
Map of filter block names to their updated values. Only changed filters are included. Each entry includes the following properties:
  • operator: Filter operator.
  • value: Current filter value.
  • modifier: Optional. Filter modifier.

embed:error

Emits when an error occurs inside the embed (e.g., invalid object name, permission denied, or token expired).

Event structure:

{
eventName: "embed:error",
payload: {
error: {
message: string,
}
}
}

Event properties:

error.message
Human-readable description of the error.

Use cases

The examples below assume you have a Holistics dashboard embedded in an iframe like this:

<iframe
id="holistics-embed"
src="https://your-holistics-domain.com/embed/EMBED_KEY?_token=JWT_TOKEN"
style="width: 100%; height: 700px; border: 0"
allowfullscreen
></iframe>

All code samples reference this iframe by its id="holistics-embed" and use https://your-holistics-domain.com as the origin for postMessage calls.

Sync filters between your app and the dashboard

Your app has its own UI controls (dropdowns, date pickers, search bars) alongside an embedded dashboard. When a user interacts with filters on either side, you want both to stay in sync.

Example: An e-commerce platform has a region dropdown in the app's header bar. Below it, a sales dashboard is embedded. When a user picks "Europe" from the dropdown, the dashboard should filter to European data. Conversely, if the user changes the region filter inside the dashboard, the app's dropdown should update to match.

This works in two directions:

App to dashboard: Your app pushes filter values into the iframe when the user interacts with your UI.

// Push filters back into the iframe
const iframe = document.getElementById("holistics-embed");

iframe.contentWindow.postMessage(
{
eventName: "dashboard:filters:apply",
payload: {
filters: {
building_id: {
operator: "is",
values: ["123"],
},
date_range: {
operator: "is_in_the_range",
values: ["last_30_days"],
},
},
},
},
"https://your-holistics-domain.com"
);

Dashboard to app: The iframe emits a dashboard:filters:applied event when the user changes a filter inside the dashboard, and your app listens and updates its own state.

// Listen for filter changes from the iframe
window.addEventListener("message", (event) => {
if (event.origin !== "https://your-holistics-domain.com") return;

if (event.data?.eventName === "dashboard:filters:applied") {
const { filters } = event.data.payload;
// Store filter state in your app
myApp.filters.set(filters);
}
});

You may want to build your own navigation for embedded dashboards: a tab-style switcher, a sidebar menu, or dashboards nested inside your app's existing navigation hierarchy. Instead of reloading the iframe each time, use page:change to switch dashboards seamlessly.

Example: A SaaS platform has a horizontal tab bar with "Overview", "Sales", and "Support" tabs. Each tab maps to a different embedded dashboard. Clicking a tab sends a page:change command, and the iframe swaps the dashboard instantly without a full reload.

const iframe = document.getElementById("holistics-embed");

function switchDashboard(dashboardName) {
iframe.contentWindow.postMessage(
{
eventName: "page:change",
payload: { path: `/objects/${dashboardName}` },
},
"https://your-holistics-domain.com"
);
}

// Wire up your custom tab navigation
document.querySelector('[data-tab="overview"]').onclick = () => switchDashboard("overview");
document.querySelector('[data-tab="sales"]').onclick = () => switchDashboard("sales_overview");
document.querySelector('[data-tab="support"]').onclick = () => switchDashboard("support_metrics");

The iframe confirms the navigation with a page:changed event, echoing back the new path so your app can track which action completed and update the active tab state.

Trigger a dashboard refresh after external changes

When your app modifies data that the dashboard depends on (e.g., user submits a form, imports a file, or completes an action), you can tell the embedded dashboard to refresh so it reflects the latest data.

async function handleFormSubmit(data) {
await saveToDatabase(data);

const iframe = document.getElementById("holistics-embed");
iframe.contentWindow.postMessage(
{ eventName: "dashboard:run", payload: {} },
"https://your-holistics-domain.com"
);
}

Track user interactions for analytics or logging

Listen for events from the embedded dashboard to understand how your users interact with analytics. You can log filter changes, navigation, and errors to your own analytics or monitoring system.

window.addEventListener("message", (event) => {
if (event.origin !== "https://your-holistics-domain.com") return;

const { eventName, payload } = event.data;

switch (eventName) {
case "dashboard:filters:applied":
analytics.track("embed_filter_changed", payload);
break;
case "page:changed":
analytics.track("embed_navigation", payload);
break;
case "embed:error":
errorTracker.capture("embed_error", payload);
break;
}
});

Handle errors and refresh expired tokens

The iframe emits embed:error events when something goes wrong (invalid object name, permission denied, or an expired token). Your app can listen for these and respond accordingly: show a contextual error message, notify an admin, or automatically refresh the token to keep the session alive.

const iframe = document.getElementById("holistics-embed");

window.addEventListener("message", async (event) => {
if (event.origin !== "https://your-holistics-domain.com") return;

if (event.data?.eventName === "embed:error") {
const { error } = event.data.payload;

if (error.code === "token_expired") {
// Generate a new token and push it into the iframe without reloading
const newToken = await generateEmbedToken();
iframe.contentWindow.postMessage(
{
eventName: "embed:token:refresh",
payload: { token: newToken },
},
"https://your-holistics-domain.com"
);
} else {
// Show error to the user or notify your team
showErrorToast(`Embed error: ${error.message}`);
notifyAdmin(error);
}
}
});

Security best practices

Always validate the event.origin before processing any message. This prevents other iframes or windows from spoofing events:

window.addEventListener("message", (event) => {
// Only accept messages from your Holistics domain
if (event.origin !== "https://your-holistics-domain.com") return;

// Process event...
});

Open Markdown
Let us know what you think about this document :)