Client API Reference
The client entry point (@eide/foir-editor-sdk) provides React components, hooks, and a GraphQL client for building editor iframes that communicate with the Foir admin.
EditorProvider
The root context provider that manages the postMessage lifecycle between your iframe and the Foir admin host. Wrap your entire application with this component.
import { EditorProvider } from '@eide/foir-editor-sdk';
function App() {
return (
<EditorProvider
parentOrigin="https://app.foir.dev"
onInit={(init) => console.log('Editor initialized', init.modelKey)}
onSaveResult={(success, error) => {
if (!success) console.error('Save failed:', error);
}}
>
<MyEditor />
</EditorProvider>
);
}Props
interface EditorProviderProps {
children: ReactNode;
parentOrigin?: string;
apiUrl?: string;
onInit?: (init: EditorInit) => void;
onValuesChanged?: (values: Record<string, unknown>) => void;
onVariantChanged?: (variantKey: string, values: Record<string, unknown>) => void;
onSaveResult?: (success: boolean, error?: string, versionId?: string) => void;
onPublishResult?: (success: boolean, error?: string) => void;
onThemeChanged?: (theme: 'light' | 'dark') => void;
onSelectionChanged?: (
keys: string[],
fields: Array<{ key: string; label: string; type: string }>
) => void;
onLocaleChanged?: (locale: LocaleContext) => void;
}| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | Required | Your editor application. |
parentOrigin | string | VITE_PARENT_ORIGIN or NEXT_PUBLIC_PARENT_ORIGIN env var, falling back to "*" | The origin of the Foir admin host window, used for postMessage validation. Set this explicitly in production. |
apiUrl | string | VITE_API_URL or NEXT_PUBLIC_API_URL env var, falling back to "http://localhost:4001/graphql" | Platform API GraphQL endpoint URL. Overridden by the apiUrl provided in the init message if present. |
onInit | (init: EditorInit) => void | — | Called when the host sends init data. Use this for one-time setup logic. |
onValuesChanged | (values: Record<string, unknown>) => void | — | Called when the host pushes updated values (e.g., from a remote save by another user). |
onVariantChanged | (variantKey: string, values: Record<string, unknown>) => void | — | Called when the user switches to a different variant in the host. |
onSaveResult | (success: boolean, error?: string, versionId?: string) => void | — | Called after a requestSave completes. Includes the new versionId on success. |
onPublishResult | (success: boolean, error?: string) => void | — | Called after a requestPublish completes. |
onThemeChanged | (theme: 'light' | 'dark') => void | — | Called when the host theme changes. The theme value from useEditor updates automatically; use this callback for side effects. |
onSelectionChanged | (keys: string[], fields: Array<...>) => void | — | Called when the user selects different fields in the host content editor (sidebar mode). |
onLocaleChanged | (locale: LocaleContext) => void | — | Called when the active locale changes in the host (sidebar mode). |
useEditor
The primary hook for interacting with the Foir admin host. Must be called inside an EditorProvider.
import { useEditor } from '@eide/foir-editor-sdk';
function MyEditor() {
const {
isReady,
init,
theme,
client,
updateField,
updateValues,
requestSave,
requestPublish,
requestOperation,
resize,
setDirty,
requestNavigate,
configMode,
selectedFieldKeys,
selectableFields,
localeContext,
} = useEditor();
if (!isReady) return null;
// ...
}Return Value
interface EditorContextValue {
isReady: boolean;
init: EditorInit | null;
theme: 'light' | 'dark';
client: PlatformClient;
updateValues: (values: Record<string, unknown>) => void;
updateField: (path: string, value: unknown) => void;
requestSave: () => void;
requestPublish: () => void;
requestOperation: (
operationKey: string,
input?: Record<string, unknown>,
options?: { mode?: 'sync' | 'async' }
) => Promise<{
success: boolean;
result?: unknown;
error?: { code: string; message: string };
}>;
resize: (height: number) => void;
setDirty: (isDirty: boolean) => void;
requestNavigate: (path: string) => void;
configMode: 'editor' | 'sidebar';
selectedFieldKeys: string[];
selectableFields: Array<{ key: string; label: string; type: string }>;
localeContext: LocaleContext | null;
}| Property | Type | Description |
|---|---|---|
isReady | boolean | true once the host has sent the init message. Always check this before accessing other values. |
init | EditorInit | null | The full initialization payload from the host. null until isReady is true. |
theme | 'light' | 'dark' | Current host theme. Updates automatically when the user toggles themes in the admin. |
client | PlatformClient | A GraphQL client pre-configured with the API URL and key from the host. Throws if called before init. |
updateValues | (values) => void | Send a complete replacement of all field values to the host. Use updateField for granular changes instead when possible. |
updateField | (path, value) => void | Send a single field update to the host. The path supports dot notation (e.g., 'content.title'). |
requestSave | () => void | Ask the host to save the current values. Listen for the result via the onSaveResult callback on EditorProvider. |
requestPublish | () => void | Ask the host to publish the current version. Listen for the result via onPublishResult. |
requestOperation | (operationKey, input?, options?) => Promise<...> | Ask the host to execute a platform operation. Pass { mode: 'sync' } (default) for inline results — times out after 60 seconds. Pass { mode: 'async' } to dispatch and receive an { executionId } immediately, then subscribe to jobs:{executionId} for progress + terminal events. |
resize | (height) => void | Report the iframe content height to the host for resizing. Prefer useAutoResize over calling this directly. |
setDirty | (isDirty) => void | Report whether the editor has unsaved changes. The host uses this to show save prompts on navigation. |
requestNavigate | (path) => void | Ask the host to navigate to an admin path (e.g., '/records/my-model'). The path must start with /. |
configMode | 'editor' | 'sidebar' | How this editor is rendered: 'editor' for main content area, 'sidebar' for the sidebar panel. |
selectedFieldKeys | string[] | Currently selected field keys in the host content editor. Only relevant in sidebar mode. |
selectableFields | Array<{ key, label, type }> | All fields available for selection in the host. Only relevant in sidebar mode. |
localeContext | LocaleContext | null | Locale information when the host is in a translation workflow. null outside of translation contexts. |
useAutoResize
Automatically reports your iframe content height to the host using a ResizeObserver. Returns a callback ref to attach to your root container element.
import { useAutoResize } from '@eide/foir-editor-sdk';
function MyEditor() {
const containerRef = useAutoResize({ minHeight: 600 });
return <div ref={containerRef}>Your editor content here</div>;
}Options
interface UseAutoResizeOptions {
/** Minimum height to report. @default 400 */
minHeight?: number;
/** Debounce delay in milliseconds. @default 100 */
debounce?: number;
/** Whether auto-resize is enabled. @default true */
enabled?: boolean;
}| Option | Type | Default | Description |
|---|---|---|---|
minHeight | number | 400 | Minimum height in pixels. Prevents the iframe from collapsing to a very small size. |
debounce | number | 100 | Delay in milliseconds before reporting height changes. Prevents excessive postMessage traffic during rapid layout changes. |
enabled | boolean | true | Set to false to disable auto-resize (e.g., when using height: 'fill' placement). |
Return Value
Returns a React.RefCallback<HTMLDivElement> — attach it to the root div of your editor. The hook observes the element’s scrollHeight and sends resize messages to the host whenever it changes.
createPlatformClient
Creates a standalone GraphQL client for the Foir platform API. You typically do not need to call this directly because EditorProvider creates one automatically (accessible via useEditor().client). Use this if you need a client outside of React context.
import { createPlatformClient } from '@eide/foir-editor-sdk';
const client = createPlatformClient({
apiUrl: 'https://api.foir.io/graphql',
apiKey: 'sk_test_...',
});
const { data } = await client.query<{ product: { title: string } }>(
`query GetProduct($id: ID!) { product(id: $id) { title } }`,
{ id: '123' }
);Options
interface PlatformClientOptions {
/** GraphQL endpoint URL. */
apiUrl: string;
/** API key for authentication. */
apiKey: string;
}PlatformClient Interface
interface PlatformClient {
query<T = Record<string, unknown>>(
query: string,
variables?: Record<string, unknown>
): Promise<GraphQLResponse<T>>;
}GraphQLResponse
interface GraphQLResponse<T = Record<string, unknown>> {
data: T | null;
errors?: GraphQLError[];
}
interface GraphQLError {
message: string;
locations?: Array<{ line: number; column: number }>;
path?: Array<string | number>;
extensions?: Record<string, unknown>;
}PlatformClientError
Thrown when a GraphQL request fails (network error or GraphQL errors in the response).
class PlatformClientError extends Error {
status?: number;
response?: unknown;
}EditorInit
The initialization payload sent by the host when your editor loads. Access it via useEditor().init.
interface EditorInit {
/** Current record field values. */
values: Record<string, unknown>;
/** Record metadata (e.g., timestamps, status). */
metadata?: Record<string, unknown>;
/** The model key for this record (e.g., 'product', 'redirect'). */
modelKey: string;
/** The record ID. Empty string for new records. */
recordId: string;
/** Human-readable natural key (slug, handle, etc.). */
naturalKey?: string;
/** Current version ID, or null for unversioned records. */
versionId: string | null;
/** Current variant key, or null if not using variants. */
variantKey: string | null;
/** Current locale code, or null. */
locale: string | null;
/** The model's field schema definition. */
schema: unknown;
/** Whether the record is editable or view-only. */
mode: 'edit' | 'readonly';
/** Current admin theme. */
theme: 'light' | 'dark';
/** API key for platform GraphQL requests. */
apiKey: string;
/** Platform API GraphQL endpoint URL. */
apiUrl: string;
/** Config connection domain (e.g., my-store.myshopify.com). */
connectionDomain?: string;
/** How this editor is rendered: 'editor' (main area) or 'sidebar'. */
configMode?: 'editor' | 'sidebar';
/** Currently selected field keys (sidebar mode). */
selectedFieldKeys?: string[];
/** Available fields for selection (sidebar mode). */
selectableFields?: Array<{ key: string; label: string; type: string }>;
/** Locale context for translation workflows (sidebar mode). */
localeContext?: LocaleContext | null;
}LocaleContext
Locale information provided to sidebar editors during translation workflows.
interface LocaleContext {
/** The locale currently being edited. */
currentLocale: string;
/** The source/default locale to translate from. */
sourceLocale: string;
/** Whether the user is actively in a translation workflow. */
isTranslating: boolean;
/** All available locales for this project. */
locales: Array<{
code: string;
displayName: string;
isDefault: boolean;
}>;
}PostMessage Protocol
The SDK handles the postMessage protocol automatically. This section documents the message types for reference. You do not need to construct these messages yourself.
Editor to Host Messages
Messages your editor sends to the Foir admin host (handled by the SDK).
| Type | Payload | Description |
|---|---|---|
ready | {} | Sent automatically when EditorProvider mounts. Signals the host to send init. |
update-values | { values: Record<string, unknown> } | Full replacement of all field values. |
update-field | { path: string, value: unknown } | Single field update. Path supports dot notation. |
request-save | {} | Request the host to save. |
request-publish | {} | Request the host to publish. |
resize | { height: number } | Report iframe content height. |
dirty | { isDirty: boolean } | Report unsaved changes state. |
request-navigate | { path: string } | Request navigation to an admin path. |
request-operation | { operationKey: string, input?: Record<string, unknown>, mode?: 'sync' | 'async', requestId: string } | Request execution of a platform operation. |
Host to Editor Messages
Messages the Foir admin host sends to your editor (handled by EditorProvider).
| Type | Payload | Description |
|---|---|---|
init | EditorInit | Initialization data. Sent once after the editor sends ready. |
values-changed | { values: Record<string, unknown>, source: 'remote' } | Values updated externally (e.g., by another user). |
variant-changed | { variantKey: string, values: Record<string, unknown> } | User switched to a different variant. |
save-result | { success: boolean, error?: string, versionId?: string } | Result of a request-save. |
publish-result | { success: boolean, error?: string } | Result of a request-publish. |
theme-changed | { theme: 'light' | 'dark' } | Admin theme changed. |
selection-changed | { selectedFieldKeys: string[], fields: Array<...> } | Field selection changed in host (sidebar mode). |
locale-changed | LocaleContext | Active locale changed (sidebar mode). |
operation-result | { requestId: string, success: boolean, result?: unknown, error?: { code, message } } | Result of a request-operation. |
Protocol Utilities
The SDK exports two validation functions and a protocol version constant, primarily useful if you need to handle raw postMessage events outside of EditorProvider:
import {
isValidEditorMessage,
isValidHostMessage,
PROTOCOL_VERSION, // '1.0'
} from '@eide/foir-editor-sdk';isValidEditorMessage(data)— type guard forEditorToHostMessageisValidHostMessage(data)— type guard forHostToEditorMessage