A field of type blocks turns a record into an ordered list of typed components (hero, feature grid, FAQ, CTA), each with its own sub-fields. Editors compose; your frontend maps type → component.
The stored shape
"content": [
{ "type": "hero", "_v": 1, "heading": "Welcome", "subheading": "…" },
{ "type": "cta", "_v": 1, "label": "Start", "url": "https://…" }
]
Why it stays safe over time
- Versioned: every block is stamped with its block-type version (
_v). Change a block's fields → bump the version → runblocks:normalizeto migrate stored content. - Tolerant: a block whose type was removed is preserved, never silently deleted. Render a fallback for unknown types in your frontend and nothing ever crashes a page.
- Validated: required sub-fields are enforced on every write path: editor, REST and MCP.
Rendering (React sketch)
const registry = { hero: Hero, cta: Cta, faq: Faq };
blocks.map((b, i) => {
const C = registry[b.type] ?? UnknownBlock; // tolerant fallback
return <C key={i} {...b} />;
});
Rule of thumb: keep anything you need to filter or sort by (title, path, status) as a top-level field; blocks are for layout, not queries.
Try it on a real project
Reading is slower than building here. Spin up a workspace and see for yourself.
Get startedKeep reading
Tutorials
Your first backend in 10 minutes
Create a project, model a resource, grab an API key and make your first authenticated requ...
TutorialsLet Claude build your backend (MCP)
Connect Claude, Cursor or ChatGPT to your personal MCP URL and build a complete backend by...
TutorialsDynamic Next.js pages backed by APIForge
One catch-all route + one pages resource = your whole marketing site, editable in the CMS...