Context, frame and analytics hooks
The host handshake, theme, sizing, the block token and settings, direct-load and nested documents, and analytics.
This page documents 10 of the 47 hooks in the Hooks reference, which indexes every hook by name. Generated from the published @civitai/blocks-react@0.73.0 package's type definitions and README.
Other pages: Generation hooks · Batch, confirmed-step and training hooks · Buzz, purchase and tip hooks · Viewer, consent and navigation hooks · Storage, picker and media hooks
useBlockContext
useBlockContext(): UseBlockContextThe primary hook. Returns everything the host delivered in `BLOCK_INIT` plus a `ready` gate — fields are sentinel-empty before init, so gate your UI on `ready`.
const { ready, context, viewer, theme, settings, blockId, blockInstanceId, appId, token, renderMode } =
useBlockContext();useBlockTheme
useBlockTheme(): UseBlockThemeThe host's CURRENT site theme, and nothing else. Same value as `useBlockContext().theme` — reach for this when theme is all you need.
function ThemedRoot() {
const theme = useBlockTheme(); // 'light' | 'dark'
return <div data-theme={theme}>…</div>;
}useBlockResize
useBlockResize(ref: RefObject<HTMLElement | null>): UseBlockResizeAttach to your root element. Observes its height and posts `RESIZE_IFRAME` so the host sizes the iframe to fit. No-op on the inline transport (host DOM reflows naturally).
const rootRef = useRef<HTMLDivElement>(null);
useBlockResize(rootRef);useBlockBreakpoint
useBlockBreakpoint(ref?: RefObject<HTMLElement | null>): UseBlockBreakpointReports the block's **own** width tier, so you can branch on "am I narrow?" without hand-rolling a `ResizeObserver` or hard-coding pixel numbers.
const bp = useBlockBreakpoint();
<div style={{ display: 'flex', flexDirection: bp.below('sm') ? 'column' : 'row' }}>
{bp.atLeast('md') && <aside>…</aside>}
</div>useBlockToken
useBlockToken(): UseBlockTokenCurrent block-scoped JWT, auto-refreshing ~2 min before expiry. Returns the token fields plus a `refresh()` for the 401-retry path, which **resolves with the new token**.
const { raw, scopes, expiresAt, buzzBudget, refresh } = useBlockToken();
let res = await fetch(url, { headers: { Authorization: `Bearer ${raw}` } });
if (res.status === 401) {
const fresh = await refresh(); // resolves WITH the new token
res = await fetch(url, { headers: { Authorization: `Bearer ${fresh.raw}` } });
}useHostOrigin
useHostOrigin(): UseHostOriginThe validated host origin to direct-fetch the App Blocks HTTP API against — `undefined` until init. Use it as the base URL when you need to bypass the host bridge, always paired with the bearer token from `useBlockToken()`.
const host = useHostOrigin(); // e.g. "https://civitai.com" (undefined until BLOCK_INIT)
const { raw } = useBlockToken();
// Once `host` is set, fetch the API on that validated origin with the block token:
if (host) {
const res = await fetch(`${host}/api/v1/blocks/me`, {
headers: { authorization: `Bearer ${raw}` },
});
}useBlockSettings
useBlockSettings(): UseBlockSettingsShorthand for `useBlockContext().settings`. Read-only from the iframe — there is no general "set settings" bridge message. Writing them is platform-side, in the settings panel reached from the **Manage** control on an installed app. The one setting a block can write itself is the viewer's checkpoint, via the `SET_USER_CHECKPOINT` message (see `useCheckpointPicker`).
const { publisherSettings, userSettings } = useBlockSettings();useBlockAnalytics
useBlockAnalytics(): UseBlockAnalyticsCustom events for your app's analytics panel. Fire-and-forget: no reply, no acknowledgement.
const { track } = useBlockAnalytics();
track('generate_clicked', { mode: 'txt2img', batch: 4 });"analytics": {
"events": {
"generate_clicked": {
"description": "User pressed Generate",
"properties": {
"mode": { "type": "enum", "values": ["txt2img", "img2img"] },
"batch": { "type": "number" },
"advanced": { "type": "boolean" }
}
}
}
}useDirectLoad
useDirectLoad(options?: UseDirectLoadOptions): UseDirectLoadDetect a DIRECT (unembedded) top-level load of a block and, after a short grace period, report it so the SDK can show an "Open on Civitai" fallback instead of hanging on the perpetual loading state. Returns `true` ONLY when BOTH hold: 1. The block is TOP-LEVEL (`window.self === window.top` — not in the host iframe), AND 2. No `BLOCK_INIT` has landed (`ready` is still `false`) within `timeoutMs`. This is precise by construction: - An EMBEDDED block (framed) is never top-level → always `false`, even before `ready`. The embedded happy path is untouched. - The dev harness / `createMockHost` runs the block top-level BUT posts `BLOCK_INIT` immediately (a `setTimeout(0)` macrotask), so `ready` flips long before `timeoutMs` and the timer is cleared → always `false`. The dev flow is untouched. - A real direct load (nobody sends `BLOCK_INIT`) stays top-level + not-ready past `timeoutMs` → `true`. Once `ready` flips it stays authoritative: this can never return `true` while `ready` is `true`, so a late init can't leave a stuck fallback.
useNestedDocument
useNestedDocument(options?: UseNestedDocumentOptions): UseNestedDocumentFetch one of your OWN bundled documents and get back a `srcdoc` string with an absolute `<base href>` injected — the only way a block can embed a nested document of its own. 🔴 PREFER NOT TO NEED THIS. A plain `<iframe src="/game/index.html">` of your own bundle **cannot load**, and no manifest change fixes it. An engine build (Defold, Unity, Phaser) is a `<canvas>` plus a JS loader and normally mounts directly into the block's own document, which avoids the whole problem class; reach for this hook only when a separate document is genuinely required. WHY, measured, in ONE place: the header of `@civitai/app-sdk`'s `src/blocks/nestedDocument.ts`. It carries the opaque-origin / `frame-ancestors` / `X-Frame-Options` derivation, the five-row DATED EXTERNAL platform matrix (verified by nothing in this repo — re-measure it if the platform's sandbox tiers or response headers change), and the known limits of the `<base>` rewrite. Do not restate any of it here: it was duplicated across 11 surfaces with nothing in the repo checking them for agreement, which is exactly how two measured claims in these files' own test headers went stale. 🔴 THE STRING IS NOT SANITIZED. It is your own markup and scripts, verbatim; pass a `src` you control, and put a `sandbox` attribute on the iframe. Changing `src` restarts the fetch and aborts the previous one — `status` goes back to `'loading'` in the SAME render that changes `src`, so no paint ever shows a stale `srcDoc` as `'ready'` beside a new `src`. Unmounting aborts the in-flight fetch, and no state is written after it. A fetch that never settles is bounded: after {@link NESTED_DOCUMENT_TIMEOUT_MS} it is aborted and `status` becomes `'error'` with a message naming the timeout. A timeout is distinguished from the hook's own aborts (unmount, a `src` change) — those stay silent, as they must.
```tsx
const { srcDoc, status, error } = useNestedDocument({ src: '/game/index.html' });
if (status === 'error') return <p>Could not load the game: {error?.message}</p>;
if (status !== 'ready') return <p>Loading…</p>;
return <iframe title="game" sandbox="allow-scripts" srcDoc={srcDoc ?? undefined} />;
```