Skip to Content
For DevelopersData model

Data model

The Blok document is a plain JSON object. You can store it wherever you want — a Postgres column, a file in object storage, a Markdown frontmatter block — and round-trip it through JSON.stringify / JSON.parse without loss.

Top-level shape

interface Data<RootProps = {}, BlockProps = {}> { root: RootData<RootProps>; content: BlockInstance<BlockProps>[]; zones?: Record<string, BlockInstance<BlockProps>[]>; } interface RootData<Props> { props?: Props; readOnly?: Partial<Record<keyof Props, boolean>>; } interface BlockInstance<Props> { type: string; props: Props & { id: string }; readOnly?: Partial<Record<keyof Props, boolean>>; version?: number; }

root

Document-level settings — whatever the root component declares as fields.

{ "root": { "props": { "title": "Welcome" } } }

content

The top-level blocks, in order.

{ "content": [ { "type": "Hero", "props": { "id": "b1", "title": "Welcome" } }, { "type": "Features", "props": { "id": "b2", "items": [...] } } ] }

zones

Nested blocks — anything inside a slot. The key is `${blockId}:${slotFieldName}`.

{ "content": [ { "type": "Container", "props": { "id": "b1" } } ], "zones": { "b1:children": [ { "type": "Hero", "props": { "id": "b2", "title": "Nested" } } ] } }

Use slotZoneId(blockId, fieldName) to compute the key:

import { slotZoneId } from "@useblok/core"; const key = slotZoneId("b1", "children"); // "b1:children"

BlockInstance

{ type: "Hero", props: { id: "b1", // unique within the document title: "Welcome", subtitle: "…", }, version: 2, // optional schema version (for migrations) }
  • type — must match a key in config.components.
  • props.id — unique block id. Blok generates these via uid().
  • version — optional. Used by the migration runner. Blocks without it are treated as v0.
  • readOnly — optional. Partial record marking specific props as read-only in the UI.

uid() helper

Blok exports a small unique-ID generator:

import { uid } from "@useblok/core"; const id = uid(); // "k3p9a-x"

Use it when you construct block instances programmatically.

Serialisation

Blok stores nothing in the data that isn’t plain JSON:

  • No Date objects — use ISO strings.
  • No functions — render, getSummary, etc. live in the Config.
  • No class instances — stick to plain objects and primitives.

This means:

const fromDB = JSON.parse(await db.pages.find(id)); <Blok config={config} data={fromDB} onSave={...} />

just works.

Compatibility

The shape is intentionally close to Puck ’s data model. If you have a Puck document, you can pass it directly as data (with minor component-name mapping). See Migrating from Puck — on our roadmap as a dedicated guide.

Last updated on