Blocks
Everything an editor puts on a page is a block. A page is not a document with regions; it is a tree of blocks, and that tree is the page's content.
A block has two halves, and holding them apart explains most of the product:
- A React component - what renders, on the server for visitors and in the canvas for editors.
- A props schema - what an editor can change. The builder generates the settings panel from this schema; nobody hand-builds a form.
Write a component and describe its props, and the block becomes something a non-technical editor can drop on a page, configure, and style.
A page is a tree
Every block in the tree carries an id and a type string, plus its own props and styles. Blocks nest: a section holds a container, which holds a heading and a button. Some blocks exist purely to hold others.
Page
└── Section
└── Container
├── Heading "Pricing"
├── Text "Simple, flat pricing"
└── Repeater over the plans collection
└── Card
├── Text {{ plan.name }}
└── Button {{ plan.ctaLabel }}
The tree is stored as JSON on the page row. It is data, not code, which is why a page can be diffed, versioned, restored, and copied between environments.
What a block declares
A block definition is mostly metadata. Grouped by what it is for:
| Group | Declares | Why it matters |
|---|---|---|
| Identity | A type string, a label, a group, an icon, an optional description | The type string is the permanent identity; the rest is how the block appears in the library |
| Children | Whether it wraps other blocks, what children it starts with, what it will accept, whether it can be nested | Turns loose blocks into a structure editors cannot easily break |
| Props | A schema plus UI hints | Becomes the settings panel |
| Special props | Which props are translatable, AI-editable, or editable inline on the canvas | The same prop can be plain text to one system and a translation target to another |
| Data | An optional data provider, and what it depends on | Lets a block fetch its own server data - see Data Providers |
| Scope | Which page types the block is allowed on | Keeps a blog-only block out of the landing-page library |
| Guardrails | Whether it can be deleted, moved, or duplicated | Editor ergonomics, not security. Anything expressed here is advisory |
The last row is worth reading twice. Block guardrails shape what the builder UI offers; they are not an authorization boundary. Access control lives in roles and permissions.
What a block receives when it renders
Beyond its own configured props, every block is handed the same render context:
| It receives | Use it for |
|---|---|
lang |
The current language |
draft |
Whether this is a draft render |
inBuilder |
Whether it is rendering in the canvas rather than for a visitor |
pageProps |
Facts about the page: slug, page type, page id, language |
pageData |
The resolved data for this render, which is what bindings read from |
inBuilder is the escape hatch that keeps the canvas usable. A carousel that autoplays for
visitors can hold still while it is being edited; a block with no content yet can render a
placeholder instead of collapsing to nothing.
Props, styles, and bindings are three different things
- Props are the block's configured values: text, a link target, a toggle, an image URL.
- Styles are Tailwind classes stored per block. Design tokens are named bundles of those classes, applied as a unit.
- Bindings are props whose value is a
{{ }}data path, resolved on the server at render time. See Data Binding.
A prop holding {{ blog.title }} is still just a prop. Nothing about the block changes; the value
arrives resolved.
One consequence catches people out: blocks store values, not references. An image block holds a URL, not a pointer to an asset record, so replacing the asset in the media library does not update pages that already use it.
Three families of block
| Family | Where it comes from | Notes |
|---|---|---|
| Shipped blocks | The builder's own library | Structural blocks, text, media, forms, repeaters |
| Your blocks | Registered by your project | Ordinary React components; this is the main extension point |
| Global blocks | Built by editors in the builder | A reusable subtree referenced from many pages: header, footer, CTA band |
A global block is not really a block type. It is a page whose block tree is referenced from other pages, so editing it once updates every page that references it. What sits in the consuming page is a lightweight reference that the server replaces with the real tree before rendering. See Global Blocks.
Type strings are effectively permanent
The stored page JSON refers to block types by string. When a page renders, each block is looked up in the registry by that string, and a block whose type is not registered renders nothing - no error, no placeholder, no visible gap in the page.
That single behaviour drives a few rules:
- Do not rename a block type once pages use it. Change the label, not the type.
- Removing a block type silently empties every page that used it. Search for usage first.
- Renaming a prop leaves the old value stranded in stored JSON. Read the old name as a fallback, or migrate the data.
The upside of the same design: a block that throws while rendering can be contained. When the app provides an error boundary to the renderer, a failing custom block degrades to nothing instead of taking the whole page down with it.
Failure modes worth knowing
| Symptom | Usual cause |
|---|---|
| A block vanished from the published page | Its type is not registered in the render path, or a condition on it resolved to false |
| A block renders in the canvas but not live | Registration happens in the editor entry but not the public route |
| Editor's text is ignored | A data provider on that block returns the same prop name; provider output wins |
| Empty text where a binding was | The bound value was missing, and a missing binding resolves to an empty string |
| Styling disappeared after a token change | Deleting a design token strips the classes it carried |
Related
- Data Providers - how a block gets server data.
- Global Blocks - reusing a subtree across pages.
- Page Types - restricting blocks to a kind of page.
- Data Binding - the
{{ }}layer over props. - Design Tokens - the styling layer over blocks.
- Custom Blocks - the registration API and full option list.
- Rendering Model - when and where the tree becomes HTML.

