---
title: "API Reference"
url: "https://glokki.com/docs/api-reference"
locale: "en"
updated: "2026-10-14T21:05:50.998Z"
---

# API Reference

Reference for the primary public exports from @glokki-cms/sdk and @glokki-cms/react.

For step-by-step integration instructions, see the [Integration Guide](/docs/integration). For block development patterns, see the [Block Development Guide](/docs/block-development).

---

## Table of Contents

### `@glokki-cms/sdk`

1. [`defineBlock()`](#defineblock)
2. [`createBlockRegistry()`](#createblockregistry)
3. [`createClient()`](#createclient)
4. [`CMSError`](#cmserror)
5. [`serializeBlockSchema()`](#serializeblockschema)

### `@glokki-cms/react`

6. [`VisualEditorProvider`](#visualeditorprovider)
7. [`useVisualEditor()`](#usevisualeditor)
8. [`CMSPageEditor`](#cmspageeditor)
9. [`CMSPage`](#cmspage)
10. [`PreviewBanner`](#previewbanner)
11. [`cmsMetadata()`](#cmsmetadata)
12. [`JsonLd`](#jsonld)
13. [`FormBlock`](#formblock)
14. [`CdnImage`](#cdnimage)
15. [`createComponentRegistry()`](#createcomponentregistry) _(deprecated)_

### Types

16. [SDK Types](#sdk-types)
17. [React Types](#react-types)

---

## `@glokki-cms/sdk`

### `defineBlock()`

The primary SDK API for block registration. Declares a block with metadata, a Zod schema, and a React component. Auto-serializes the Zod schema into `FieldDescriptor[]` so the CMS never touches Zod directly.

```ts
import { defineBlock } from "@glokki-cms/sdk"
```

#### Signature

```ts
function defineBlock<TSchema, TComponent>(
  config: DefineBlockConfig<TSchema, TComponent>
): BlockRegistryEntry<InferOutput<TSchema>, TComponent>
```

#### `DefineBlockConfig`

| Property           | Type                                              | Required | Description                                                                                                                                                                                                                                                 |
| ------------------ | ------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`             | `string`                                          | Yes      | Unique block type key (e.g., `"hero"`, `"rich-text"`)                                                                                                                                                                                                       |
| `label`            | `string`                                          | Yes      | Human-readable display name                                                                                                                                                                                                                                 |
| `category`         | `BlockCategory` (a `string`)                      | Yes      | Category key this block is grouped under. Any key without whitespace, 1–64 characters. Must appear in the registry's `categories` option when one is declared; otherwise use one of the CMS built-ins (`content`, `layout`, `media`, `interactive`, `list`) |
| `icon`             | `string`                                          | Yes      | Lucide icon name for the component library browser                                                                                                                                                                                                          |
| `description`      | `string`                                          | No       | Short description shown in the component library browser                                                                                                                                                                                                    |
| `defaultProps`     | `InferOutput<TSchema>`                            | Yes      | Default props when adding a new instance (type-safe, inferred from schema)                                                                                                                                                                                  |
| `propsSchema`      | `TSchema`                                         | Yes      | Zod `z.object()` schema describing the block's props                                                                                                                                                                                                        |
| `viewportDefaults` | `ViewportOverrides<TProps>`                       | No       | Per-viewport prop defaults (`{ tablet?: Partial<TProps>, mobile?: Partial<TProps> }`)                                                                                                                                                                       |
| `slots`            | `SlotDefinition[] \| (props) => SlotDefinition[]` | No       | Slot definitions for blocks that can contain child blocks. Static array or a function that derives slots from current props                                                                                                                                 |
| `component`        | `TComponent`                                      | Yes      | React component that renders the block                                                                                                                                                                                                                      |

#### Returns

A `BlockRegistryEntry` with pre-serialized `fields: FieldDescriptor[]`, ready for `createBlockRegistry()`.

#### Example

```ts
import { z } from "zod"
import { defineBlock } from "@glokki-cms/sdk"
import { HeroComponent } from "./hero"

const heroSchema = z.object({
  heading: z.string().min(1).max(120).describe("Heading"),
  subheading: z.string().max(240).optional().describe("Subheading"),
  alignment: z.enum(["left", "center", "right"]).default("center"),
})

export const HeroBlock = defineBlock({
  type: "hero",
  label: "Hero",
  category: "content",
  icon: "Sparkles",
  defaultProps: {
    heading: "Hello World",
    alignment: "center",
  },
  propsSchema: heroSchema,
  component: HeroComponent,
})
```

#### Errors

Throws if `propsSchema` is not a `z.object()` schema (e.g., `z.record()`, `z.string()`). Only `z.object()` schemas can be serialized into field descriptors.

---

### `createBlockRegistry()`

Creates a typed block registry from an array of `defineBlock()` results. The registry provides component lookup, wire-safe serialized definitions for the iframe handshake, and type introspection.

```ts
import { createBlockRegistry } from "@glokki-cms/sdk"
```

#### Signature

```ts
function createBlockRegistry(
  entries: BlockRegistryEntry[],
  options?: BlockRegistryOptions
): BlockRegistry
```

#### `BlockRegistryOptions`

| Property     | Type                        | Required | Description                                                                                                                                               |
| ------------ | --------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `categories` | `BlockCategoryDefinition[]` | No       | The tenant's own ordered `{ key, label }` block categories. Omit it to keep the CMS's built-in five (`content`, `layout`, `media`, `interactive`, `list`) |

A declared list **replaces** the CMS's built-in categories — it never merges
with them — and the block picker renders the categories in the array's order.
Keys and labels must be non-empty and at most 64 characters, and a key may not
contain whitespace. Every block's `category` must appear in the list;
`createBlockRegistry` throws at build time otherwise.

#### `BlockRegistry`

| Method                       | Returns                                           | Description                                                                                                                                             |
| ---------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `getComponent(type)`         | `unknown \| undefined`                            | Look up the rendering component for a block type                                                                                                        |
| `getSerializedDefinitions()` | `SerializedBlockDefinition[]`                     | Wire-safe definitions for the iframe handshake (no Zod, no React)                                                                                       |
| `getTypes()`                 | `string[]`                                        | All registered block type strings (e.g., `["hero", "cta"]`)                                                                                             |
| `getCategories()`            | `readonly BlockCategoryDefinition[] \| undefined` | The categories declared via `options.categories`, in declaration order, or `undefined` when none were declared. Sent to the CMS on the iframe handshake |
| `resolveSlots(type, props)`  | `SlotDefinition[] \| undefined`                   | Resolve slot definitions for a block given its current props. Evaluates dynamic slot functions                                                          |
| `hasDynamicSlots(type)`      | `boolean`                                         | Returns `true` if a block's slots are defined as a function of props                                                                                    |

#### Example

```ts
import { createBlockRegistry, defineBlock } from "@glokki-cms/sdk"

const HeroBlock = defineBlock({ type: "hero" /* ... */ })
const CtaBlock = defineBlock({ type: "cta" /* ... */ })

const registry = createBlockRegistry([HeroBlock, CtaBlock], {
  categories: [
    { key: "content", label: "Content" },
    { key: "interactive", label: "Interactive" },
  ],
})

registry.getTypes() // ["hero", "cta"]
registry.getComponent("hero") // HeroComponent
registry.getSerializedDefinitions() // SerializedBlockDefinition[]
registry.getCategories() // the two declared categories, in order
```

#### Errors

Throws if duplicate block types are detected in the `entries` array, and — when
`categories` is declared — if the list is empty, holds a duplicate key, has an
invalid key or label, or omits a category that one of the blocks names.

---

### `createClient()`

Creates a typed client for the CMS Delivery API. Supports fetching pages, navigation, redirects, A/B experiment resolution, and collections.

```ts
import { createClient } from "@glokki-cms/sdk"
```

#### Signature

```ts
function createClient(options: ClientOptions): CMSClient
```

#### `ClientOptions`

| Property       | Type          | Required | Description                                                                                      |
| -------------- | ------------- | -------- | ------------------------------------------------------------------------------------------------ |
| `baseUrl`      | `string`      | Yes      | Base URL of the CMS API (e.g., `"http://localhost:3000"`)                                        |
| `apiKey`       | `string`      | No       | API key for authentication (sent as `Authorization: Bearer` header)                              |
| `tenantSlug`   | `string`      | No       | Tenant slug (sent as `X-Tenant-Slug` header)                                                     |
| `preview`      | `boolean`     | No       | Enable preview mode to fetch draft content                                                       |
| `previewToken` | `string`      | No       | Preview token for draft content access                                                           |
| `fetchInit`    | `RequestInit` | No       | Default `RequestInit` merged into every fetch call. Controls framework-level caching (see below) |

The `fetchInit` option lets you control Next.js caching behavior:

```ts
// Always fresh (disables ISR/SSG)
createClient({ baseUrl, fetchInit: { cache: "no-store" } })

// On-demand revalidation via tags
createClient({ baseUrl, fetchInit: { next: { tags: ["cms"] } } })

// ISR with 60-second TTL
createClient({ baseUrl, fetchInit: { next: { revalidate: 60 } } })
```

#### `CMSClient`

The client object returned by `createClient()` exposes these namespaces:

##### `client.pages`

| Method                   | Returns                         | Description                                                                                       |
| ------------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------- |
| `getBySlug(slug, opts?)` | `Promise<DeliveryPage \| null>` | Fetch a published page by slug. Returns `null` for 404. Supports `locale` and `visitorId` options |
| `list(opts?)`            | `Promise<{ data, meta }>`       | List published pages. Returns `DeliveryPageListItem[]` with `PaginationMeta`                      |

##### `client.navigation`

| Method       | Returns                     | Description                                                      |
| ------------ | --------------------------- | ---------------------------------------------------------------- |
| `get(opts?)` | `Promise<NavigationItem[]>` | Fetch navigation items (slug, title, locale) for published pages |

##### `client.redirects`

| Method   | Returns                       | Description                     |
| -------- | ----------------------------- | ------------------------------- |
| `list()` | `Promise<DeliveryRedirect[]>` | Fetch all active redirect rules |

##### `client.experiments`

| Method                                           | Returns                              | Description                                                                                                                     |
| ------------------------------------------------ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `resolveVariant(experimentId, visitorId, opts?)` | `Promise<VariantResolution \| null>` | Resolve which A/B variant a visitor should see. Deterministic assignment. Pass `{ recordImpression: true }` to track atomically |
| `recordImpression(experimentId, visitorId)`      | `Promise<void>`                      | Record an impression. Do NOT use after `pages.getBySlug()` (it already records impressions)                                     |
| `recordConversion(experimentId, visitorId)`      | `Promise<void>`                      | Record a conversion event for an experiment                                                                                     |

##### `client.collections`

| Method                     | Returns                               | Description                                                                                                      |
| -------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `list(opts?)`              | `Promise<{ data, meta }>`             | List all collections for the tenant                                                                              |
| `getBySlug(slug)`          | `Promise<DeliveryCollection \| null>` | Fetch a collection by slug. Returns `null` for 404                                                               |
| `listEntries(slug, opts?)` | `Promise<{ entries, pagination }>`    | List published entries. Supports pagination, sorting, tag filtering, locale filtering, and dynamic field filters |

##### `ListCollectionEntriesOptions`

| Property        | Type                     | Description                                                                                             |
| --------------- | ------------------------ | ------------------------------------------------------------------------------------------------------- |
| `page`          | `number`                 | Page number (default: 1)                                                                                |
| `limit`         | `number`                 | Entries per page (max: 100)                                                                             |
| `sortBy`        | `string`                 | Sort field: `"published_at"`, `"created_at"`, `"updated_at"`, `"title"`, or a sortable custom field key |
| `sortDirection` | `"asc" \| "desc"`        | Sort direction                                                                                          |
| `locale`        | `string`                 | Filter by locale                                                                                        |
| `tag`           | `string`                 | Filter by tag slug                                                                                      |
| `fields`        | `Record<string, string>` | Dynamic field filters (e.g., `{ author: "John" }`)                                                      |

**Field filters.** `fields` (or `?field.<key>=<value>` on the raw endpoint) filters on custom fields that are sortable, shown in the list, or filterable. Text fields match partially, select/boolean exactly, and date/number fields accept `<key>_gte` / `<key>_lte`. Data model reference fields accept a record UUID **or a record key** (`{ topics: "seo" }`); a multi reference (`dataModelRefs`) matches entries that contain the record. A key that matches no record returns an empty list rather than ignoring the filter. Repeating a parameter on the raw endpoint (`?field.topics=seo&field.topics=performance`) requires all of them.

#### Example

```ts
const cms = createClient({
  baseUrl: process.env.CMS_BASE_URL!,
  tenantSlug: process.env.CMS_TENANT!,
  apiKey: process.env.CMS_API_KEY!,
  fetchInit: { next: { tags: ["cms-pages"] } },
})

// Fetch a page
const page = await cms.pages.getBySlug("about")

// List pages
const { data: pages, meta } = await cms.pages.list({ limit: 10 })

// Collections
const { entries } = await cms.collections.listEntries("blog", {
  page: 1,
  limit: 10,
  sortBy: "published_at",
  sortDirection: "desc",
  tag: "featured",
})
```

---

### `CMSError`

Error class thrown when the Delivery API returns an error response.

```ts
import { CMSError } from "@glokki-cms/sdk"
```

#### Properties

| Property  | Type     | Description                                                         |
| --------- | -------- | ------------------------------------------------------------------- |
| `code`    | `string` | Machine-readable error code (e.g., `"NOT_FOUND"`, `"UNAUTHORIZED"`) |
| `message` | `string` | Human-readable error message                                        |
| `status`  | `number` | HTTP status code                                                    |

#### Example

```ts
try {
  // Note: pages.getBySlug() returns null for 404, not an error.
  // CMSError is thrown for non-404 errors like auth failures.
  const nav = await cms.navigation.get()
} catch (error) {
  if (error instanceof CMSError) {
    console.log(error.code) // "UNAUTHORIZED"
    console.log(error.status) // 401
  }
}
```

---

### `serializeBlockSchema()`

Low-level utility that converts a Zod `z.object()` schema into `FieldDescriptor[]`. Used internally by `defineBlock()` — you typically don't need to call this directly.

```ts
import { serializeBlockSchema } from "@glokki-cms/sdk"
```

#### Signature

```ts
function serializeBlockSchema(schema: ZodObjectSchema): FieldDescriptor[]
```

---

## `@glokki-cms/react`

### `VisualEditorProvider`

Context provider that manages visual editor state. In normal mode, renders the `fallbackTree` with zero overhead. In editor mode (when loaded inside the CMS iframe), initiates a postMessage handshake with the CMS and syncs the component tree, selection, and viewport in real-time.

```tsx
import { VisualEditorProvider } from "@glokki-cms/react"
```

#### Props (`VisualEditorProviderProps`)

| Prop             | Type                | Required | Description                                                                                                                                                                                                                                                                                       |
| ---------------- | ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `registry`       | `ComponentRegistry` | Yes      | Component registry mapping block types to React components. Should be a stable reference (module-level or memoized)                                                                                                                                                                               |
| `blockRegistry`  | `BlockRegistry`     | No       | Typed block registry from `createBlockRegistry()`. Enables v2 handshake with full block definitions and dynamic slot updates                                                                                                                                                                      |
| `layoutRegistry` | `LayoutRegistry`    | Yes      | Layout registry from `createLayoutRegistry()`. Serialized into the handshake and used to render header/footer chrome around the block tree                                                                                                                                                        |
| `fallbackTree`   | `ComponentNode[]`   | No       | Component tree for non-editor mode. Typically from the Delivery API (default: `[]`)                                                                                                                                                                                                               |
| `pageSlug`       | `string`            | No       | Page slug sent in the handshake (e.g., `"/about"`)                                                                                                                                                                                                                                                |
| `tenantSlug`     | `string`            | No       | Tenant slug this app serves (e.g., `process.env.NEXT_PUBLIC_CMS_TENANT`). Enables the cross-tenant guard: on a mismatch with the editing session's tenant the CMS shows a connection error and the provider withholds its `ready` reply, so no draft content is exchanged. Omit to skip the check |
| `children`       | `ReactNode`         | Yes      | Child components                                                                                                                                                                                                                                                                                  |

#### Behavior

- **Normal mode**: Uses `fallbackTree` as the component tree. No postMessage bridge overhead.
- **Editor mode** (detected via `?_editor=1&_editorOrigin=...` query params): Initiates handshake with the CMS, then receives `tree:update`, `block:select`, `viewport:change`, `block:props-update`, `block:scroll-into-view`, and `token:refresh` messages.

#### Example

```tsx
<VisualEditorProvider
  registry={registry}
  blockRegistry={blockRegistry}
  layoutRegistry={layoutRegistry}
  fallbackTree={page.componentTree}
  pageSlug="/about"
  tenantSlug={process.env.NEXT_PUBLIC_CMS_TENANT}
>
  <CMSPageEditor />
</VisualEditorProvider>
```

---

### `useVisualEditor()`

Hook that reads the visual editor context. Must be called within a `<VisualEditorProvider>`.

```tsx
import { useVisualEditor } from "@glokki-cms/react"
```

#### Signature

```ts
function useVisualEditor(): VisualEditorContextValue
```

#### `VisualEditorContextValue`

| Property          | Type                | Description                                                                                |
| ----------------- | ------------------- | ------------------------------------------------------------------------------------------ |
| `componentTree`   | `ComponentNode[]`   | Current component tree (from editor or fallback)                                           |
| `selectedBlockId` | `string \| null`    | ID of the currently selected block                                                         |
| `viewport`        | `Viewport`          | Current viewport: `"desktop"` \| `"tablet"` \| `"mobile"`                                  |
| `isEditorMode`    | `boolean`           | Whether the page is loaded inside the CMS editor iframe                                    |
| `registry`        | `ComponentRegistry` | The component registry passed to the provider                                              |
| `editorOrigin`    | `string \| null`    | Origin of the CMS editor window, or `null` when not in editor mode                         |
| `isLibraryOpen`   | `boolean`           | Whether the block library panel is open in the CMS sidebar                                 |
| `isTreeSynced`    | `boolean`           | `true` after the first `tree:update` message is received. Always `true` in non-editor mode |
| `editorToken`     | `string \| null`    | Current editor session token (refreshed periodically). `null` when not in editor mode      |

#### Example

```tsx
function MyBlock() {
  const { isEditorMode, selectedBlockId, viewport } = useVisualEditor()

  return (
    <div>
      {isEditorMode && <span>Editing</span>}
      {viewport === "mobile" && <MobileLayout />}
    </div>
  )
}
```

#### Errors

Throws if called outside a `<VisualEditorProvider>`.

---

### `CMSPageEditor`

Editor-aware page renderer. Reads the component tree from `VisualEditorContext`, wraps each block in `<EditableBlock>` for click/hover selection, and renders `<InsertionIndicator>` and `<BlockGapInserter>` for visual insertion feedback.

```tsx
import { CMSPageEditor } from "@glokki-cms/react"
```

#### Props

None. Reads all state from `VisualEditorContext`.

#### Behavior

- Renders each block using the component registry with interactive editing wrappers
- Shows an empty canvas placeholder when the tree is empty (editor mode only)
- Renders insertion indicators at the computed insertion position when the block library is open
- Reports `tree:rendered` back to the CMS after each render cycle

#### Example

```tsx
<VisualEditorProvider registry={registry} pageSlug="/about">
  <CMSPageEditor />
</VisualEditorProvider>
```

---

### `CMSPage`

Static page renderer for non-editor contexts. Renders a component tree using a registry without any editor interactivity. Use this for published page rendering.

```tsx
import { CMSPage } from "@glokki-cms/react"
```

#### Props

| Prop            | Type                | Required | Description                                      |
| --------------- | ------------------- | -------- | ------------------------------------------------ |
| `componentTree` | `ComponentNode[]`   | Yes      | The component tree from published page data      |
| `registry`      | `ComponentRegistry` | Yes      | Registry mapping block types to React components |

#### Example

```tsx
<CMSPage componentTree={page.componentTree} registry={registry} />
```

---

### `PreviewBanner`

Amber warning banner displayed at the top of the viewport during preview mode. Shows "Preview mode --- this page is not published" with a dismiss button. Uses inline styles so tenant apps don't need Tailwind or any CSS framework.

```tsx
import { PreviewBanner } from "@glokki-cms/react"
```

#### Props

None.

#### Example

```tsx
export default async function Page({ searchParams }) {
  const { preview } = await searchParams
  const isPreview = preview === "true"
  return (
    <>
      {isPreview && <PreviewBanner />}
      <MainContent />
    </>
  )
}
```

---

### `cmsMetadata()`

Generates a Next.js `Metadata` object from a CMS page's SEO data. Handles title, description, canonical URL, robots, Open Graph, Twitter Card, and hreflang alternates.

```ts
import { cmsMetadata } from "@glokki-cms/react"
```

#### Signature

```ts
function cmsMetadata(
  page: DeliveryPage | null,
  options?: CmsMetadataOptions
): Metadata
```

#### `CmsMetadataOptions`

| Property  | Type     | Description                                                                                                                                                                                                    |
| --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `baseUrl` | `string` | Base URL of the tenant's site (e.g., `"https://acme.editor.app"`). Used for canonical URL and Open Graph URL generation                                                                                        |
| `env`     | `string` | Runtime environment (default: `process.env.APP_ENV`). Any value other than `"production"` — **including unset** — forces `robots: { index: false, follow: false }`, overriding the page's own robots directive |

#### Behavior

- Returns `{ title: "Page not found", robots: { index: false } }` when `page` is `null`
- **Non-production is always `noindex, nofollow`.** Unless `env` (or `APP_ENV`) is exactly `"production"`, robots are forced to `{ index: false, follow: false }` so staging and preview deploys are never indexed. Set `APP_ENV=production` on your production deployment, or every page will be de-indexed. In production, the page's stored robots string is honoured (`"none"` = `noindex, nofollow`)
- JSON-LD is **not** included (Next.js Metadata API does not support it). Use the `<JsonLd>` component separately

#### Example

```tsx
import { cmsMetadata, JsonLd } from "@glokki-cms/react"

export async function generateMetadata({ params }) {
  const { slug } = await params
  const page = await cms.pages.getBySlug(slug)
  return cmsMetadata(page, { baseUrl: "https://acme.editor.app" })
}

export default async function Page({ params }) {
  const { slug } = await params
  const page = await cms.pages.getBySlug(slug)
  return (
    <>
      <JsonLd data={page.seo.jsonLd} />
      <CMSPage componentTree={page.componentTree} registry={registry} />
    </>
  )
}
```

---

### `JsonLd`

Renders JSON-LD structured data as `<script type="application/ld+json">` tags. Escapes closing script tags to prevent XSS. Each item in the array becomes a separate script tag.

```tsx
import { JsonLd } from "@glokki-cms/react"
```

#### Props

| Prop   | Type                                               | Required | Description                                                                                   |
| ------ | -------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------- |
| `data` | `SeoMetadata["jsonLd"] \| Record<string, unknown>` | Yes      | JSON-LD objects from the page's SEO data. Accepts an array or a single object (legacy format) |

Renders nothing when `data` is `undefined` or an empty array, so you can pass `page.seo.jsonLd` without a guard.

#### Example

```tsx
<JsonLd data={page.seo.jsonLd} />
```

---

### `FormBlock`

Client component that renders a form built in the CMS (**Forms**), validates it in the browser, and submits it to the Delivery API. Field definitions, the success message, the redirect URL, and spam protection (honeypot, optional Turnstile/reCAPTCHA) come from the form's settings in the CMS.

```tsx
import { FormBlock } from "@glokki-cms/react"
```

#### Props

| Prop              | Type                                           | Required | Description                                                                                             |
| ----------------- | ---------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------- |
| `client`          | `FormBlockClient`                              | Yes      | A delivery client from `createClient()` (anything with `forms.getById` and `forms.submit`). Memoize it. |
| `formId`          | `string`                                       | Yes      | The published form's id.                                                                                |
| `initialForm`     | `DeliveryForm`                                 | No       | The form definition, fetched on the server, so the form renders without a client-side request.          |
| `onSubmitSuccess` | `(response: { submissionId: string }) => void` | No       | Called after a successful submission, before the redirect (if the form has one).                        |
| `onSubmitError`   | `(error: unknown) => void`                     | No       | Called with the raw error when submission fails.                                                        |
| `submitLabel`     | `string`                                       | No       | Submit button text. Default `"Submit"`. Since 0.10.0.                                                   |
| `submittingLabel` | `string`                                       | No       | Button text while the submission is in flight. Default `"Submitting…"`. Since 0.10.0.                   |

Validation messages and the loading text are English and not yet configurable.

#### Styling

The component renders unstyled markup with stable hooks: the root carries `data-form-block` and `data-status` (`loading`, `ready`, `submitting`, `success`, `error`); the button has `data-form-submit`, and a submission error is a `data-form-error` element with `role="alert"`.

#### Example

The form fetches and submits from the browser, so the client needs a key there: use a delivery key (`delivery:read` scope), which is meant to be public, and keep admin keys server-side.

```tsx
"use client"

import { FormBlock } from "@glokki-cms/react"
import { createClient } from "@glokki-cms/sdk"

const client = createClient({
  baseUrl: process.env.NEXT_PUBLIC_CMS_BASE_URL!,
  tenantSlug: process.env.NEXT_PUBLIC_CMS_TENANT!,
  apiKey: process.env.NEXT_PUBLIC_CMS_API_KEY!,
})

export function ContactForm({ formId }: { formId: string }) {
  return (
    <FormBlock
      client={client}
      formId={formId}
      submitLabel="Send message"
      submittingLabel="Sending…"
    />
  )
}
```

---

### `CdnImage`

Renders an image prop served through your `/cdn/` route with a responsive `srcSet` and the asset's focal point. Any non-`/cdn/` URL renders as a plain `<img>`. Since 0.10.0. Props and the generated markup are described in [Asset CDN Proxy](/docs/asset-cdn-proxy#rendering-with-a-cdnimage-component).

```tsx
import { CdnImage } from "@glokki-cms/react"

;<CdnImage
  src={image}
  alt={alt}
  width={1200}
  height={800}
  sizes="(min-width: 768px) 50vw, 100vw"
/>
```

---

### `createComponentRegistry()`

> **Deprecated.** Use `createBlockRegistry()` from `@glokki-cms/sdk` instead.

Creates a simple `Map<string, Component>` from a plain object. Does not support the v2 handshake, block metadata, or dynamic slots.

```ts
import { createComponentRegistry } from "@glokki-cms/react"
```

#### Signature

```ts
function createComponentRegistry(
  components: Record<string, ComponentType<Record<string, unknown>>>
): ComponentRegistry
```

#### Migration

```ts
// Before (deprecated)
const registry = createComponentRegistry({
  hero: HeroComponent,
  cta: CtaComponent,
})

// After (recommended)
import { defineBlock, createBlockRegistry } from "@glokki-cms/sdk"

const HeroBlock = defineBlock({ type: "hero", ..., component: HeroComponent })
const CtaBlock = defineBlock({ type: "cta", ..., component: CtaComponent })
const registry = createBlockRegistry([HeroBlock, CtaBlock])
```

---

## Additional Exports

The packages export several additional utilities and components beyond the primary APIs documented above. These are typically used for advanced integrations or internal purposes.

### `@glokki-cms/sdk` — Additional

| Export                                         | Description                                                                      |
| ---------------------------------------------- | -------------------------------------------------------------------------------- |
| `sendMessage(target, type, payload, origin)`   | Send a typed postMessage to the CMS or iframe                                    |
| `onMessage(origin, handler)`                   | Listen for typed postMessages from a specific origin. Returns a cleanup function |
| `createMessage(type, payload, seq?, version?)` | Create a protocol-stamped message envelope                                       |
| `isVisualEditorMessage(value)`                 | Runtime type guard for visual editor messages                                    |
| `buildEditorIframeUrl(params)`                 | Construct the iframe `src` URL for the visual editor                             |
| `generateCollectionTypes(options)`             | Generate TypeScript types for a single collection                                |
| `generateAllCollectionTypes(options)`          | Generate TypeScript types for all tenant collections                             |
| `SDK_VERSION`                                  | SDK version string sent in the handshake request                                 |

### `@glokki-cms/react` — Additional

| Export                | Description                                                    |
| --------------------- | -------------------------------------------------------------- |
| `EditableBlock`       | Wrapper component for click/hover selection in editor mode     |
| `BlockGapInserter`    | Click-to-insert button rendered between blocks in editor mode  |
| `InsertionIndicator`  | Visual indicator showing where the next block will be inserted |
| `VisualEditorContext` | Raw React context (use `useVisualEditor()` hook instead)       |
| `SDK_VERSION`         | SDK version constant                                           |

---

## SDK Types

Key types exported from `@glokki-cms/sdk`:

### Block Types

| Type                                     | Description                                                                                                  |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `BlockRegistryEntry<TProps, TComponent>` | Result of `defineBlock()` with pre-serialized field descriptors                                              |
| `DefineBlockConfig<TSchema, TComponent>` | Configuration object passed to `defineBlock()`                                                               |
| `BlockCategory`                          | A block's category key — a free-form `string`, not a closed union (the tenant declares its own set)          |
| `BlockCategoryDefinition`                | One declared category: `{ key: string, label: string }`                                                      |
| `SerializedBlockCategory`                | Wire form of a category — identical to `BlockCategoryDefinition`                                             |
| `BlockRegistryOptions`                   | Second argument to `createBlockRegistry()`: `{ categories?: BlockCategoryDefinition[] }`                     |
| `BlockRegistry`                          | Registry interface returned by `createBlockRegistry()`                                                       |
| `SerializedBlockDefinition`              | Wire-safe block definition sent during iframe handshake                                                      |
| `SlotDefinition`                         | Named slot area where child blocks can be dropped                                                            |
| `SerializedSlotDefinition`               | Wire-safe slot definition (static or dynamic)                                                                |
| `BlockViewportOverrides<TProps>`         | Per-viewport prop overrides for block definitions (`{ tablet?: Partial<TProps>, mobile?: Partial<TProps> }`) |
| `ViewportOverrides`                      | Non-generic viewport overrides for delivery API wire types                                                   |

### Delivery API Types

| Type                   | Description                                                                                   |
| ---------------------- | --------------------------------------------------------------------------------------------- |
| `ClientOptions`        | Options for `createClient()`                                                                  |
| `CMSClient`            | Client type returned by `createClient()`                                                      |
| `DeliveryPage`         | Published page with component tree, SEO, code injection                                       |
| `DeliveryPageListItem` | Lightweight page item from the list endpoint                                                  |
| `ComponentNode`        | A node in the component tree (`id`, `type`, `props`, `children`, `slot`, `viewportOverrides`) |
| `SeoMetadata`          | SEO fields: meta title, description, canonical, robots, OG, Twitter, JSON-LD                  |
| `CodeInjection`        | Custom `<head>` and `<body>` code snippets                                                    |
| `NavigationItem`       | Published page navigation entry (`slug`, `title`, `locale`)                                   |
| `DeliveryRedirect`     | Redirect rule (`source`, `target`, `type`, `isRegex`, `preserveQueryString`)                  |
| `PaginationMeta`       | Pagination metadata (`total`, `limit`, `offset`)                                              |
| `ApiError`             | Error response shape from the Delivery API                                                    |
| `AlternateLocale`      | Alternate locale link for hreflang (`locale`, `url`)                                          |
| `ExperimentMeta`       | A/B experiment metadata (`experimentId`, `variantId`, `variantName`, `isControl`)             |
| `VariantResolution`    | Variant resolution result (extends `ExperimentMeta` with `pageId`)                            |

### Collection Types

| Type                           | Description                                                                                                                                                                                                                |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DeliveryCollection`           | Collection with fields schema, sort config, and URL prefix                                                                                                                                                                 |
| `CollectionEntry`              | Published collection entry with `collectionData`, SEO, and tags                                                                                                                                                            |
| `CollectionEntryTag`           | Tag attached to an entry (`id`, `name`, `slug`)                                                                                                                                                                            |
| `CollectionEntriesPagination`  | Pagination for entries (`page`, `limit`, `total`, `totalPages`)                                                                                                                                                            |
| `CollectionFieldDefinition`    | Field definition within a collection schema                                                                                                                                                                                |
| `CollectionFieldType`          | Field types: `"text"`, `"textarea"`, `"richtext"`, `"number"`, `"date"`, `"datetime"`, `"boolean"`, `"url"`, `"image"`, `"file"`, `"select"`, `"multiselect"`, `"relation"`, `"dataModelRef"`, `"dataModelRefs"`, `"json"` |
| `ListCollectionEntriesOptions` | Options for listing collection entries                                                                                                                                                                                     |

### Field Descriptor Types

| Type              | Description                                         |
| ----------------- | --------------------------------------------------- |
| `FieldDescriptor` | Serialized field definition for the CMS props panel |
| `FieldType`       | Field type string used in descriptors               |
| `FieldValidation` | Validation rules (min, max, pattern, etc.)          |
| `JsonValue`       | JSON-safe value type                                |

### Visual Editor Protocol Types

| Type                  | Description                                        |
| --------------------- | -------------------------------------------------- | --- |
| `VisualEditorMessage` | Base message type for CMS <-> iframe communication |
| `ProtocolVersion`     | Protocol version number (`1                        | 2`) |
| `CMSToIframeMessage`  | Union of all message types from CMS to iframe      |
| `IframeToCMSMessage`  | Union of all message types from iframe to CMS      |
| `Viewport`            | `"desktop" \| "tablet" \| "mobile"`                |

### Protocol Constants

| Export                      | Value             | Description                                                  |
| --------------------------- | ----------------- | ------------------------------------------------------------ |
| `PROTOCOL_VERSION`          | `2`               | Current protocol version (numeric)                           |
| `SUPPORTED_VERSIONS`        | `[1, 2]`          | Supported protocol versions (numeric array)                  |
| `NAMESPACE`                 | `"__editorCms"`   | Message namespace guard — filters foreign postMessage events |
| `EDITOR_QUERY_PARAM`        | `"_editor"`       | URL param that enables editor mode                           |
| `EDITOR_ORIGIN_QUERY_PARAM` | `"_editorOrigin"` | URL param containing the CMS origin                          |
| `EDITOR_TOKEN_QUERY_PARAM`  | `"_editorToken"`  | URL param containing the editor session token                |
| `EDITOR_LOCALE_QUERY_PARAM` | `"locale"`        | URL param for locale override inside the editor iframe       |

---

## React Types

Key types exported from `@glokki-cms/react`:

| Type                        | Description                                                    |
| --------------------------- | -------------------------------------------------------------- |
| `VisualEditorProviderProps` | Props for `<VisualEditorProvider>`                             |
| `VisualEditorContextValue`  | Return type of `useVisualEditor()`                             |
| `ComponentRegistry`         | `Map<string, ComponentType>` mapping block types to components |
| `CmsMetadataOptions`        | Options for `cmsMetadata()`                                    |
| `EditableBlockProps`        | Props for the `<EditableBlock>` wrapper (internal)             |
| `BlockGapInserterProps`     | Props for `<BlockGapInserter>` (internal)                      |
| `InsertionIndicatorProps`   | Props for `<InsertionIndicator>` (internal)                    |
