Skip to main content

Iframe Quickstart

This is the fastest way to try the Developer API without building a backend first.

In iframe mode, your app talks to the Chastify parent via postMessage, and Chastify calls server APIs on your behalf.

What Chastify Passes To Your Iframe

On open, Chastify puts a JSON payload in location.hash.
Important fields:

  • bridge.nonce: request signing value for bridge messages.
  • bridge.parentOrigin: required target origin for postMessage.
  • sessionId: stable extension session id for this lock.
  • lockId: active lock id.
  • ui: theme values from parent page.
  • Optional: homeActionSlug, homeAction, intent, regularActionsSummary, mainToken.

Use helpers from apps/extension/src/lib/ChastifyBridge.ts:

  • parseHashPayload()
  • ChastifyBridgeClient
  • startAutoResizeToParent(...)
  • themeVars(...)

Minimal React bootstrap

import { useEffect, useMemo, useState } from "react";
import {
parseHashPayload,
ChastifyBridgeClient,
startAutoResizeToParent,
themeVars,
} from "../lib/ChastifyBridge";

const payload = parseHashPayload();
if (!payload?.bridge?.nonce || !payload?.bridge?.parentOrigin) {
throw new Error("Missing bridge payload in iframe hash");
}

export function App() {
const [client, setClient] = useState<ChastifyBridgeClient | null>(null);
const [session, setSession] = useState<any>(null);
const [stateDoc, setStateDoc] = useState<any>(null);
const vars = useMemo(() => themeVars(payload.ui ?? null), []);

useEffect(() => {
const c = new ChastifyBridgeClient({
nonce: payload.bridge!.nonce,
targetOrigin: payload.bridge!.parentOrigin,
});
setClient(c);
return () => c.destroy();
}, []);

useEffect(() => {
if (!client) return;
(async () => {
const s = await client.request("session.get", {});
setSession(s);

const st = await client.request("state.get", {});
setStateDoc(st);
})().catch(console.error);
}, [client]);

useEffect(() => {
return startAutoResizeToParent({
nonce: payload.bridge!.nonce,
targetOrigin: payload.bridge!.parentOrigin,
extraPx: 12,
});
}, []);

return (
<div style={{ background: vars.pageBg, color: vars.text, minHeight: "100%" }}>
<h2>Extension Quickstart</h2>
<pre>{JSON.stringify(session, null, 2)}</pre>
<pre>{JSON.stringify(stateDoc, null, 2)}</pre>
</div>
);
}

Raw postMessage Example (No Helper Class)

If you do not use ChastifyBridgeClient, send the full request envelope manually:

const req = {
type: "chastify:ext:req",
v: 1,
id: crypto.randomUUID(),
nonce,
action: "session.get",
payload: {},
};

window.parent.postMessage(req, parentOrigin);

Listen for matching responses:

window.addEventListener("message", (event) => {
if (event.origin !== parentOrigin) return;
const msg = event.data;
if (!msg || msg.type !== "chastify:ext:resp" || msg.v !== 1) return;
if (msg.id !== req.id) return;

if (msg.ok) console.log("Bridge success:", msg.data);
else console.error("Bridge error:", msg.error);
});

Test Flow You Should Run First

  1. session.get
  2. state.get
  3. metadata.get
  4. regularActions.get

This confirms bridge auth, safe reads, and request/response handling. State writes must be performed by your backend with Developer API credentials.

File Uploads From Iframe

If your extension needs images or generated media, use the bridge file helpers. Chastify uploads to managed R2 storage, returns a stable file id, and gives you a short-lived signed URL for rendering.

const capabilities = await client.filesCapabilities();
if (capabilities.enabled) {
const uploaded = await client.filesUpload({
file,
filename: file.name,
purpose: "puzzle-image",
});

// Send uploaded.file.id to your backend if it should be stored in session state.

const refreshed = await client.filesGet(uploaded.file.id);
image.src = refreshed.file.signedUrl;
}

Store uploaded.file.id, not the signed URL. Signed URLs expire and should be refreshed with files.get / client.filesGet(...) when the iframe loads again.

Theme Support (ui + themeVars)

Chastify passes a ui object in the iframe hash.
themeVars(ui) converts it into usable design tokens:

  • pageBg
  • text
  • panel
  • border
  • muted
  • inputBg

Use these values as your extension’s base styling so your iframe matches host theme.

Live theme updates

The parent can send chastify:ext:ui events while your iframe is open.
If you want live theme sync, listen for that event and update local theme state.

For detailed light/dark theme handling, contrast examples, and Tailwind patterns, see Iframe Theming.

Auto Resize Support

Use startAutoResizeToParent(...) so the parent can resize the iframe to your content.

Why this matters:

  • Prevents scroll traps inside the iframe.
  • Keeps setup modals and extension pages sized correctly.
  • Works well for dynamic sections that expand/collapse.

Common mistakes

  • Wrong targetOrigin (must match bridge.parentOrigin).
  • Missing or stale nonce.
  • Not calling destroy() on unmount for ChastifyBridgeClient.
  • Sending unsupported actions without checking session.get capabilities first.

Reference Files

  • apps/extension/src/lib/ChastifyBridge.ts
  • apps/extension/src/pages/MainPage.tsx
  • apps/extension/src/pages/SetupPage.tsx