Skip to main content
Embedded Cube surfaces communicate with your host page over the browser postMessage API, in both directions:
  • Events (cube:event:*) travel embed → host. Subscribe to them to learn how a viewer interacts with the embed — when it loads, what they view, download, drill into, search for with AI, and any errors they hit. Feed them into your own analytics / event bus.
  • Actions (cube:action:*) travel host → embed. Send them to drive the embed from your app — switch the color scheme, set a filter, navigate, or refresh the data.
No SDK is required — it’s plain window.postMessage and a message listener.

Message envelope

Every message — in either direction — is a single object with the same shape:
{
  source: "cube-embed",              // discriminator — always this string
  direction: "event" | "action",     // "event" = embed→host, "action" = host→embed
  type: string,                      // e.g. "cube:event:download" / "cube:action:set-filter"
  payload: object,                   // shape depends on `type` (see catalogs below)
  timestamp: number,                 // epoch milliseconds
  surface?: "dashboard" | "app" | "chat" // always on events; never on actions
}
FieldDescription
sourceAlways "cube-embed". Check this first to tell Cube messages apart from other postMessage traffic on the page (browser extensions, other libraries, your own app).
direction"event" for messages emitted by the embed, "action" for messages you send into it.
typeThe event or action name (see the catalogs below).
payloadEvent/action-specific data.
timestampWhen the message was created, in epoch milliseconds.
surfaceWhich embedded surface the message relates to. Always present on events; never sent on actions — so event listeners never need to handle a missing surface.

Listening to events

Attach a single message listener to window. Always validate event.origin against your tenant’s origin and check data.source === "cube-embed" before trusting a message.
const CUBE_ORIGIN = "https://your-tenant.cubecloud.dev";

window.addEventListener("message", (event) => {
  // 1. Only trust messages from your Cube tenant.
  if (event.origin !== CUBE_ORIGIN) return;

  const data = event.data;

  // 2. Only handle Cube embed events.
  if (!data || data.source !== "cube-embed" || data.direction !== "event") return;

  // 3. Dispatch on the event type.
  switch (data.type) {
    case "cube:event:ready":
      console.log("Embed ready", data.payload.embedTenant, data.payload.deploymentId);
      break;
    case "cube:event:download":
      myAnalytics.track("embed_download", data.payload);
      break;
    case "cube:event:ai-query":
      myAnalytics.track("embed_ai_query", { query: data.payload.query });
      break;
    case "cube:event:error":
      console.error("Embed error", data.payload.message);
      break;
    default:
      // ready, view, navigate, dashboard-loaded, drilldown, …
      myAnalytics.track(data.type, { surface: data.surface, ...data.payload });
  }
});

Event catalog

EventFires whenSurfaces
cube:event:readyThe embed has authenticated and mounted (the handshake)all
cube:event:viewA surface is viewed, on load and on each in-embed navigationall
cube:event:navigateThe viewer navigates within the embedall
cube:event:dashboard-loadedAll widgets on a dashboard have rendereddashboard
cube:event:downloadThe viewer exports data or an imagedashboard
cube:event:drilldownThe viewer drills into a measuredashboard
cube:event:ai-queryThe viewer runs an AI / natural-language queryall
cube:event:errorThe embed surfaces an errorall
Every event payload is also delivered with the envelope’s surface field, so you can always tell which surface (dashboard, app, or chat) it came from — including AI queries, which report app when run inside the embedded app and chat on the standalone chat surface.

cube:event:ready

Emitted once per session, as soon as the embed authenticates and mounts. The handshake — the first event you receive, and the moment to record an “embed opened”.
FieldTypeDescription
embedTenantstring | nullThe embed tenant the iframe resolved to, when known.
deploymentIdnumber | nullThe deployment the embed is bound to, when known.
mode"signed" | "private"How the viewer was authenticated.
surface"dashboard" | "app" | "chat"The surface that mounted.
publicIdstring (optional)The dashboard’s public id, for the dashboard surface.
{
  "embedTenant": "acme",
  "deploymentId": 42,
  "mode": "signed",
  "surface": "dashboard",
  "publicId": "a1b2c3d4"
}

cube:event:view

Emitted when a surface is viewed — on the initial load and again whenever the viewer navigates within the embed.
FieldTypeDescription
surface"dashboard" | "app" | "chat"The surface viewed.
pathstringThe in-embed route path that was viewed.
publicIdstring (optional)The dashboard’s public id, when applicable.
titlestring (optional)Human-readable title of the surface, when available.
{
  "surface": "app",
  "path": "/embed/d/42/app/workbook/130"
}

cube:event:navigate

Emitted when the viewer navigates within the embed (a route change). Use it to mirror the embed’s location in your own router or analytics.
FieldTypeDescription
pathstringThe new path.
previousPathstring (optional)The path navigated away from.
{
  "path": "/embed/d/42/app/workbook/130",
  "previousPath": "/embed/d/42/app"
}

cube:event:dashboard-loaded

Emitted when a dashboard has finished rendering all of its widgets — the “fully painted” signal (distinct from ready, which fires at mount, before data loads).
FieldTypeDescription
publicIdstring (optional)The dashboard’s public id.
widgetCountnumber (optional)Number of widgets on the dashboard.
loadDurationMsnumber (optional)Milliseconds from mount to all widgets loaded, when measurable.
{
  "publicId": "a1b2c3d4",
  "widgetCount": 6
}

cube:event:download

Emitted when a viewer exports something — a widget’s data as CSV, or (in future) a dashboard image. Reports that an export happened and its shape — never the exported rows themselves.
FieldTypeDescription
format"csv" | "xlsx" | "png" | "pdf"The file format produced.
target"widget" | "dashboard"Whether a single widget or the whole dashboard was exported.
widgetIdstring (optional)Id of the source widget, when target is "widget".
titlestring (optional)Title of the exported widget / dashboard.
rowCountnumber (optional)Rows exported, for data exports (csv / xlsx).
{
  "format": "csv",
  "target": "widget",
  "widgetId": "37",
  "title": "Revenue by month",
  "rowCount": 128
}
The CSV download action on a dashboard widget only appears when the embed URL includes allowExport=true (see Dashboards → Allow CSV export). The event fires when a viewer uses it.

cube:event:drilldown

Emitted when a viewer drills into a measure (clicks a chart mark or table cell to see its detail rows).
FieldTypeDescription
memberstringThe fully-qualified measure that was drilled into.
valueunknown (optional)The clicked value, when the click carried one.
widgetIdstring (optional)Id of the originating widget.
{
  "member": "orders.count",
  "value": "completed",
  "widgetId": "37"
}

cube:event:ai-query

Emitted around an AI / natural-language query — capturing what the viewer asked and the lifecycle stage. Fires wherever AI chat is used: the standalone chat surface, the dashboard agent, and the embedded app.
FieldTypeDescription
querystringThe natural-language query the viewer submitted.
status"submitted" | "completed" | "error"Lifecycle stage of the query.
chatIdstring (optional)The chat/session id, when applicable.
agentIdstring (optional)The agent that answered, when applicable.
{
  "query": "top 10 customers by revenue this quarter",
  "status": "submitted",
  "agentId": "1"
}

cube:event:error

Emitted when the embed surfaces an error (a render error, a query failure, an auth/session problem). fatal distinguishes an error that took the whole surface down from a recoverable one.
FieldTypeDescription
messagestringHuman-readable message.
namestring (optional)Error name/class, e.g. "TypeError".
contextstring (optional)Where it originated, e.g. "embed-render".
fatalboolean (optional)true when the error took down the whole surface.
{
  "message": "Failed to load data",
  "context": "embed-render",
  "fatal": true
}

Sending actions

Send actions into the embed by posting a message to the iframe’s contentWindow. Always target your tenant’s origin (not "*") so the message can’t leak to another document if the iframe navigates away.
const iframe = document.querySelector("iframe#cube");
const CUBE_ORIGIN = "https://your-tenant.cubecloud.dev";

function sendAction(type, payload = {}) {
  iframe.contentWindow.postMessage(
    {
      source: "cube-embed",
      direction: "action",
      type,
      payload,
      timestamp: Date.now(),
    },
    CUBE_ORIGIN
  );
}

// Examples
sendAction("cube:action:set-color-scheme", { scheme: "dark" });
sendAction("cube:action:set-filter", {
  filterUrlParameter: 'f_orders.status={"value":"completed"}',
});
sendAction("cube:action:refresh");

Action catalog

ActionEffectPayload
cube:action:set-color-schemeSwitch light / dark / auto{ scheme }
cube:action:set-themeApply a brand theme (colors, fonts)embedTheme object
cube:action:set-localeSwitch the UI language{ locale }
cube:action:set-filterPush a filter into a dashboard{ filterUrlParameter }
cube:action:navigateNavigate the embed to a path{ path }
cube:action:refreshRe-run the embed’s queriesnone

cube:action:set-color-scheme

Switch the embed’s color scheme at runtime.
FieldTypeDescription
scheme"light" | "dark" | "auto""auto" follows the viewer’s OS preference.
sendAction("cube:action:set-color-scheme", { scheme: "dark" });

cube:action:set-theme

Apply a brand theme (colors, fonts) to the embed at runtime. The payload is an embedTheme object — the same shape the Generate Session API accepts. Common fields are primaryColor, borderRadius, and font; see App customization for the full list.
sendAction("cube:action:set-theme", {
  primaryColor: "#7c5cff",
  borderRadius: 8,
});

cube:action:set-locale

Switch the embed’s UI language. Accepts a full code (es-ES), a short code (es), or a regional variant. See Localization for the list of supported languages and the other ways to set the language.
FieldTypeDescription
localestringThe locale to switch to.
sendAction("cube:action:set-locale", { locale: "es" });

cube:action:set-filter

Push a filter into a dashboard. The filterUrlParameter is the same f_<semantic_view>.<dimension>=<JSON> form used to pre-set filters via URL, so you can capture a viewer’s filters and restore them later.
FieldTypeDescription
filterUrlParameterstringFilter(s) in URL-query form, e.g. f_orders.status={"value":"completed"}.
sendAction("cube:action:set-filter", {
  filterUrlParameter: 'f_orders.status={"value":"completed"}',
});

cube:action:navigate

Navigate the embed to an in-embed path.
FieldTypeDescription
pathstringThe in-embed path to navigate to.
sendAction("cube:action:navigate", { path: "/embed/d/42/app/workbook/130" });

cube:action:refresh

Re-run the embed’s queries and refresh its data. No payload.
sendAction("cube:action:refresh");

Surfaces

Events come from one of three customer-facing surfaces, reported in the envelope’s surface field:

Security

  • Always validate event.origin against your tenant’s origin in your message listener, and check data.source === "cube-embed". Never act on a message that fails either check.
  • Target your tenant’s origin when sending actions (iframe.contentWindow.postMessage(msg, CUBE_ORIGIN)), not "*", so an action can’t be delivered to an unexpected document.

Complete example

A minimal host page that loads a signed dashboard embed, logs every event, and exposes buttons to drive it. Generate the session on your backend with the Generate Session API — see Signed embedding for the full flow.
<!doctype html>
<html>
  <body>
    <button id="dark">Dark mode</button>
    <button id="refresh">Refresh</button>

    <iframe
      id="cube"
      title="Dashboard"
      src="https://your-tenant.cubecloud.dev/embed/dashboard/YOUR_DASHBOARD_PUBLIC_ID?session=YOUR_SESSION_ID"
      width="100%"
      height="800"
    ></iframe>

    <script>
      const CUBE_ORIGIN = "https://your-tenant.cubecloud.dev";
      const iframe = document.getElementById("cube");

      // Receive events (embed → host)
      window.addEventListener("message", (event) => {
        if (event.origin !== CUBE_ORIGIN) return;
        const data = event.data;
        if (!data || data.source !== "cube-embed" || data.direction !== "event") return;

        console.log(`[${data.surface}] ${data.type}`, data.payload);
        // → forward to your own analytics / event bus here
      });

      // Send actions (host → embed)
      function sendAction(type, payload = {}) {
        iframe.contentWindow.postMessage(
          { source: "cube-embed", direction: "action", type, payload, timestamp: Date.now() },
          CUBE_ORIGIN
        );
      }

      document.getElementById("dark").onclick = () =>
        sendAction("cube:action:set-color-scheme", { scheme: "dark" });
      document.getElementById("refresh").onclick = () =>
        sendAction("cube:action:refresh");
    </script>
  </body>
</html>