Skip to Content
Getting StartedCore concepts

Core concepts

Blok is driven by two plain objects: a config (what blocks exist and how they look) and a data document (the current content). Everything you see in the editor is derived from these.

<Blok config={config} data={data} onSave={...} onPublish={...} />

Config

The Config object declares:

  • components — your block library. Each key is a block type.
  • root — optional root wrapper (an HTML scaffold, theme provider, layout grid).
  • categories — optional grouping for the block palette.
config.ts
import type { Config } from "@useblok/core"; export const config: Config = { components: { Hero: { /* ... */ }, Features: { /* ... */ }, Testimonial: { /* ... */ }, }, root: { fields: { description: { type: "textarea", label: "SEO description" }, }, render: ({ children }) => <main>{children}</main>, }, categories: { marketing: { title: "Marketing" }, layout: { title: "Layout" }, }, };

Components

A component is a user-definable block type. It has:

  • fields — field definitions used to generate the auto-form.
  • defaultProps — values used when the block is inserted.
  • render — the React component drawn on the canvas.
  • getSummary — short label shown in the Layers tree and row headings.
Hero: { label: "Hero", description: "Big banner with a headline.", folder: "Marketing", // grouping in the Blocks palette accent: "#6366f1", // icon tint fields: { title: { type: "text", label: "Title", required: true }, subtitle: { type: "textarea", label: "Subtitle", rows: 3 }, }, defaultProps: { title: "Hello", subtitle: "" }, render: (props) => <section>{props.title}</section>, getSummary: (props) => props.title as string, }

See the full field reference for all 12 field types.

Data

The data document is a plain JSON object:

interface Data { root: { props?: Record<string, unknown> }; content: BlockInstance[]; // top-level blocks zones?: Record<string, BlockInstance[]>; // nested (slot) blocks } interface BlockInstance { type: string; // matches config.components[type] props: { id: string } & Record<string, unknown>; version?: number; // for migrations }

A minimal document:

{ "root": { "props": { "title": "Untitled" } }, "content": [ { "type": "Hero", "props": { "id": "abc", "title": "Welcome" } } ] }

The shape is JSON-serialisable by design: save it, ship it between client and server, diff it, version it.

Slots (nested blocks)

A slot field lets a block contain other blocks — e.g. a Container that wraps a stack of other blocks.

Container: { fields: { children: { type: "slot", label: "Contents" }, }, render: ({ blok }) => ( <div style={{ padding: 24 }}> {blok.renderSlot("children")} </div> ), }

Slot contents live in data.zones keyed by `${blockId}:${slotFieldName}`. Use the slotZoneId(blockId, name) helper if you need to compute it yourself.

Root

The root component wraps the whole document. Use it for an HTML scaffold, a theme provider, or a layout grid. It has its own fields (so the document can have settings like title, slug, meta description) and optional render.

root: { fields: { title: { type: "text", label: "Page title" }, description: { type: "textarea", label: "SEO description" }, }, defaultProps: { title: "Untitled" }, render: ({ children, title }) => ( <main> <title>{title}</title> {children} </main> ), }

Only one root exists per document. The Config panel in the editor exposes its fields.

The <Blok /> component

Everything above is passed to a single React component:

<Blok config={config} data={initialData} title="My page" onSave={(data) => { /* persist */ }} onPublish={(data) => { /* go live */ }} />

The editor owns the document state once mounted — don’t pass data as a controlled prop. Treat it as the initial state. You receive the current document in onSave, onPublish, and similar callbacks.

See the full <Blok /> reference →.

Last updated on