Skip to main content

Overview

When you embed a shared dashboard (/share/application/:id) as an iframe, Upsolve keeps its URL filter state in sync with your host application over the browser postMessage API. This lets you:
  • Read the embedded view’s filter state — e.g. mirror it into your own URL so the dashboard’s filtered state is deep-linkable and survives a refresh.
  • Drive the embedded view from your own UI — push filter changes down without reloading the iframe.
The iframe emits a message on every filter change (the filter omnibar updates the URL via the History API, and we emit on those updates too — not just full navigations), accepts filter params you push down, and announces when it is ready to receive them.
This applies to embeds of /share/application/:id. Filters are expressed as URL query params — see URL Filter Parameters for the f_ omnibar param format.

Message schema

Every message has a type and a numeric version v. Pin to the version you build against; we bump v for any breaking change to a payload shape.
DirectiontypePayloadMeaning
iframe → parentembed:ready{ v }The iframe’s listener is live. Safe to send the initial params.
iframe → parentembed:params{ v, search }The iframe’s filter state changed. search is a query string (leading ?) containing only filter params (f_* / filter_*) — auth tokens and other internal params are intentionally stripped (see Security). Empty string means no active filters.
parent → iframeembed:set-params{ v?, search }Apply these params to the embedded view. search may include or omit the leading ?; an empty string clears all filters. Messages with a v newer than the iframe understands are ignored.
Current version: v: 1.

Security: lock down origins

The iframe never posts to "*". Instead, you declare your origin to the iframe via a parentOrigin query param in the iframe src:
https://ai-hub.upsolve.ai/share/application/{appId}?jwt={token}&parentOrigin=https://your-app.com
The iframe validates parentOrigin is a well-formed http(s) origin, only accepts messages whose event.origin matches it, and only posts back to it. On your side, always validate event.origin against the iframe’s origin before trusting a message, and pass an explicit targetOrigin when you post — never "*".
embed:params only ever carries filter params (f_* / filter_*). The iframe deliberately strips auth tokens (jwt, supabaseToken, dbAuthToken) and internal params (parentOrigin, transparent) before posting, so mirroring embed:params.search into your own URL bar will never expose an Upsolve token in your address bar, browser history, or Referer headers. Keep those tokens in the iframe src you construct — they don’t need to round-trip through postMessage.

Consumer reference implementation

Drop this into the page that hosts the iframe. It mirrors the iframe’s filter state into your own URL bar, and (optionally) pushes your URL’s filters down on load.
const IFRAME_ORIGIN = "https://ai-hub.upsolve.ai";

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

// Build the iframe URL: forward your filters + declare your origin.
iframe.src =
  `${IFRAME_ORIGIN}/share/application/${appId}?jwt=${token}` +
  `${location.search ? "&" + location.search.slice(1) : ""}` +
  `&parentOrigin=${encodeURIComponent(location.origin)}`;

window.addEventListener("message", (e) => {
  // 4. Origin lockdown — only trust the embedded iframe.
  if (e.origin !== IFRAME_ORIGIN) return;

  // 3. Handshake — push the initial params once the iframe is listening.
  if (e.data?.type === "embed:ready") {
    iframe.contentWindow.postMessage(
      { type: "embed:set-params", v: 1, search: location.search },
      IFRAME_ORIGIN
    );
  }

  // 1. The iframe's filters changed — mirror them into your URL bar.
  if (e.data?.type === "embed:params") {
    const url = new URL(location.href);
    url.search = e.data.search;
    history.replaceState(null, "", url); // mirror up without spamming back button
  }
});

Pushing filters live

To change the embedded view’s filters from your own UI after load — without reloading the iframe — post an embed:set-params message at any time:
function setEmbedFilter(search) {
  iframe.contentWindow.postMessage(
    { type: "embed:set-params", v: 1, search },
    IFRAME_ORIGIN
  );
}

// e.g. filter to a single discipline
setEmbedFilter("f_Raw.olympics_summer.discipline=sm:Athletics");

// e.g. clear all filters
setEmbedFilter("");
The dashboard re-renders against the new filters in place.

Notes & gotchas

  • Send params only after embed:ready. If you post embed:set-params before the iframe is listening, it is silently dropped. Wait for the handshake.
  • embed:params echoes. After you apply embed:set-params, the iframe will emit an embed:params reflecting the canonical (normalised) query string. The reference handler above mirrors it with history.replaceState, which does not re-emit — so there is no feedback loop.
  • search is the source of truth. Both directions speak the URL query string, so the same f_/filter_ param formats documented in URL Filter Parameters apply.
  • Auth tokens stay in the iframe src. Keep jwt / dbAuthToken in the iframe URL you construct; they don’t need to be (and shouldn’t be) round-tripped through postMessage.