Skip to content
13Reference

Embedding

The script loader and the plain iframe, every attribute, the postMessage contract in both directions, the window.Spatly helpers and domain restriction.

A live one, first

The block below is a real published Insight Container running in an iframe on this page, mounted when it scrolled into view. Press Open insights. The card posts spatly:expand, and the loader grows the frame.

Scroll to load the live embed
/embed/G9P5cdBirtlazy

Where the snippet comes from

Every published surface hands you both forms on its share page.

The embed code panel on the share page: the script loader and the plain iframe, each with a copy button.
  1. Script loader: a div plus an async script. This is the form that resizes itself.
  2. Copy: puts it on the clipboard with your public id already filled in.
  3. iframe: the same surface with no JavaScript on your side.
  4. Public id: the only thing that differs between two embeds.

Script loader

One tag, no dependencies, about 2 KB. Two forms; the second is what the share page gives you, because it lets you place the frame exactly where you want it.

the script places the frame after itself
<script src="https://spatly.io/embed.js" data-spatly="<publicId>" data-height="560"></script>
or you place the element and the script scans for it
<div data-spatly="<publicId>" data-height="560"></div>
<script async src="https://spatly.io/embed.js"></script>

Attributes

AttributeDefaultMeaning
data-spatlyrequiredthe public id of the surface (required)
data-height560initial height in px; the frame follows spatly:resize afterwards
data-variantthe author'slight · dark · night · ops
data-expandedoff1 starts an Insight Container expanded

The loader may appear any number of times on one page; each frame is tracked separately. Elements already mounted are marked data-spatly-mounted and skipped, so calling window.Spatly.scan() after inserting new markup is safe.

Plain iframe

If you would rather own the element:

html
<iframe
  src="https://spatly.io/embed/<publicId>?variant=dark"
  width="100%" height="560" style="border:0;border-radius:8px"
  loading="lazy" allow="fullscreen; clipboard-write" allowfullscreen
  title="Seismic activity, last 24h · Spatly"></iframe>

Query parameters on /embed/<publicId>: variant, expanded=1, beat=<beatId>, f=<filters>, and #ch=<n> for a story chapter. Without the loader you have to handle spatly:resize yourself, because Insight Containers change height when they expand.

Messages: runtime → host

The runtime posts to window.parent with target origin *. Check event.origin on your side if the page is sensitive.

typePayloadWhen
spatly:ready{ publicId }the surface has rendered
spatly:resize{ height }the content height changed
spatly:expand{ expanded }an Insight Container opened or closed
spatly:beat{ beatId }the active beat changed (chapter, slide, bookmark)
spatly:select{ datasetId, featureIds }the reader selected features
host page
window.addEventListener("message", (e) => {
  if (e.origin !== "https://spatly.io") return;
  const m = e.data;
  if (m?.type === "spatly:select") console.log("selected", m.datasetId, m.featureIds);
});

Messages: host → runtime

Send the same shapes back to drive the surface:

typePayloadEffect
spatly:goto{ sceneIndex }go to chapter / slide n
spatly:beat{ beatId }fly to a saved beat
spatly:setTheme{ variant }light · dark · night · ops
spatly:expand{ expanded }open or collapse an Insight Container
spatly:select{ datasetId, featureIds }highlight features and run the actions graph
js
const frame = document.querySelector("iframe[src*='spatly.io/embed']");
frame.contentWindow.postMessage({ type: "spatly:goto", sceneIndex: 2 }, "https://spatly.io");

window.Spatly

The loader exposes a small API so you do not have to find the iframe yourself:

js
Spatly.on("ready",  (d) => console.log("ready", d.publicId));
Spatly.on("select", (d) => setSelection(d.featureIds));
Spatly.on("*",      (name, d) => console.debug(name, d));   // every event
Spatly.off("select", handler);

Spatly.goto("<publicId>", 2);              // scene index
Spatly.expand("<publicId>", true);         // open an Insight Container
Spatly.setTheme("<publicId>", "night");
Spatly.mount(element, { id, height, variant, expanded, title });   // mount by hand
Spatly.scan();                             // mount any new [data-spatly] elements
Spatly.instances                           // { publicId: { id, iframe, el, expanded } }

Handlers receive the message payload plus publicId and the iframe element that sent it, so one handler can serve several embeds on the page.

Sizing behaviour

  • The loader sets the initial height from data-height.
  • On spatly:resize it sets the frame to the reported content height, unless the container is expanded.
  • On spatly:expand with expanded: true it grows the frame to min(90vh, 900px) and remembers the previous height; on collapse it restores it.

A dashboard reports a stable height and simply fills it. A story is scroll-driven, so give it a real height (600 to 800 px) rather than relying on resize.

Content Security Policy

If your site sets a CSP, allow the frame and its own connections:

text
frame-src https://spatly.io;
script-src https://spatly.io;      # only if you use embed.js

The embed's own network calls (tiles, live metrics) happen inside the iframe and are governed by Spatly's policy, not yours.

Domain restriction

Set the surface to domain visibility and list the hosts allowed to frame it. The direct link then answers 403 and the runtime refuses to render anywhere else. Use it for internal portals where the link must not travel. See Publishing & sharing.

If Allow embedding is off, the surface renders at its own link but not in a frame anywhere.

Analytics

Embedded surfaces record the same view, scene, expand and select events as a direct visit, so you can see whether the card on a partner's page is actually opened. No personal data is collected.

tipan Insight Container inside your own dashboard should start collapsed and expand in place. Use data-expanded only when the expanded state is the content.

Verified on 2026-09-02. The embed above is a live /embed/<publicId> frame of a published sample surface on this deployment.

Was this page helpful?

Something wrong or missing? Write to hello@spatly.io.