Editor SDK
The Foir Editor SDK lets you build custom editor interfaces that run inside the Foir admin as iframes. Your editor communicates with the host application through a bi-directional postMessage protocol, giving you full control over the editing experience while staying integrated with the platform’s save, publish, and versioning workflows.
Package
npm install @foir/sdkEntry Points
The SDK ships two entry points with no cross-dependencies:
| Entry point | Import path | Runtime | Purpose |
|---|---|---|---|
| Client | @foir/sdk | Browser (React) | Iframe communication, hooks, platform GraphQL client |
| Server | @foir/sdk/server | Node.js / Cloudflare Workers | GraphQL queries, scoped-token and webhook verification, async-callback client |
The client entry point requires React 18 or later. The server entry point has zero dependencies and uses the Web Crypto API (Node 18+, Cloudflare Workers, Deno).
Architecture
+---------------------------+ postMessage +---------------------------+
| Foir Admin (host) | <---------------------> | Your Editor (iframe) |
| | | |
| - Sends init data | EditorToHostMessage | - EditorProvider |
| - Receives field updates | HostToEditorMessage | - useEditor() hook |
| - Handles save/publish | | - useAutoResize() hook |
| - Executes operations | | - PlatformClient |
+---------------------------+ +---------------------------+
|
| fetch (GraphQL)
v
+---------------------------+
| Foir Platform API |
+---------------------------+When your iframe loads, it sends a ready message. The host responds with an init message containing the record’s current values, schema, theme, API credentials, and other context. From there, your editor sends field updates, save requests, and operation requests back to the host.
Quick Start: Client
Wrap your application root with EditorProvider, then use the useEditor hook in any child component:
import { EditorProvider, useEditor, useAutoResize } from '@foir/sdk';
function App() {
return (
<EditorProvider>
<MyEditor />
</EditorProvider>
);
}
function MyEditor() {
const { isReady, init, updateField, setDirty } = useEditor();
const containerRef = useAutoResize({ minHeight: 600 });
if (!isReady || !init) return null;
const title = (init.values.title as string) ?? '';
return (
<div ref={containerRef}>
<input
value={title}
onChange={(e) => {
updateField('title', e.target.value);
setDirty(true);
}}
/>
</div>
);
}EditorProvider handles the full postMessage lifecycle: sending the ready signal, receiving init data, creating a platform GraphQL client, and syncing theme changes. You do not need to manage any of this manually.
Quick Start: Server
Use createServerClient in your API backend to query platform data. .query() is a thin transport over the per-model public GraphQL API — each model exposes its own typed list and singular queries (products, product(id:)). Typed per-model queries are the recommended path; generic record(id:) / records(modelKey:) escape-hatch queries also exist (untyped JSON).
import { createServerClient } from '@foir/sdk/server';
const client = createServerClient({
baseUrl: 'https://api.foir.dev',
apiKey: 'sk_project_xxx',
});
// Query platform data via the per-model public API
const { data } = await client.query(`
query {
products(first: 10) {
edges { node { _id title } }
}
}
`);Record writes do not go through a dedicated method — issue the per-model create<Model> / update<Model> mutation through client.query(...). (The editor’s scoped-token surface also exposes an upsertRecord create-or-merge mutation, but API-key callers use the per-model mutations.)
Quick Start: Operation Verification
When the platform calls your operation endpoint, verify the scoped token in X-Foir-Token against the platform’s JWKS endpoint before processing:
import {
verifyScopedToken,
parseSubject,
type OperationPayload,
} from '@foir/sdk/server';
const JWKS_URL = 'https://api.foir.dev/.well-known/jwks.json';
app.post('/operations/my-op', async (c) => {
const token = c.req.header('x-foir-token');
if (!token) return c.json({ error: 'missing token' }, 401);
const claims = await verifyScopedToken(token, JWKS_URL);
const { appName } = parseSubject(claims.sub);
const payload = (await c.req.json()) as OperationPayload;
console.log(payload.operationKey, payload.input, 'caps:', claims.cap);
return c.json({ success: true, result: { processed: true } });
});Scoped tokens are the only inbound auth path the platform emits. The legacy HMAC verifyOperationSignature / verifyAndParseOperation helpers were removed in v0.8.0; if you are upgrading from v0.7.x, see Server API Reference.
Next Steps
- Client API Reference — full documentation of
EditorProvider,useEditor,useAutoResize, andcreatePlatformClient - Server API Reference — full documentation of
createServerClient, operation verification, and webhook helpers - Examples — real-world patterns from production extensions