Build pages from blocks
Most pages are a stack of sections: a hero, some features, a quote, a call to action. A Blocks field keeps those sections inside the page entry itself, each one typed, validated and delivered in order. Editors get collapsible cards to add, reorder and remove; your front end gets a predictable list it can map to components.
Blocks or references?
- Use a Blocks field for sections that belong to one page. They live in the entry, publish with it, and never show up as separate entries in the content list.
- Use a References field for content shared between pages, like an author, a testimonial used on three pages, or a site-wide call to action.
- Never park page structure in a JSON field. It gets no editor, no validation and no media handling.
Define the block types
In the model editor, add a field of type Blocks to your page type and describe its block types in the box. One block type per line, its fields indented beneath as name: type. Add ! to make a field required on publish, and put select options in brackets.
hero: Hero heading: symbol! body: rich_text image: media features: Feature list title: symbol! items: text quote: Quote text: text! author: symbol tone: select(light, dark)
Block fields can be symbol, text, rich_text, number, boolean, date, select, media, location or json. Reference fields inside blocks are not supported yet - if a section needs to point at another entry, use a References field on the page for now.
Set Min and Max on the field to limit how many blocks a page can have, for example exactly one hero.
Or define them over the API
The same model as a field definition, for scripts and agents. config.blocks maps each block type to its label and fields:
curl -X POST https://mojimoto.com/api/v1/your-project/manage/content-types/page/fields \
-H "Authorization: Bearer mjmt_your_write_token" \
-H "Content-Type: application/json" \
-d '{
"field": "sections",
"definition": {
"type": "blocks",
"config": {
"label": "Sections",
"blocks": {
"hero": {
"label": "Hero",
"fields": {
"heading": { "type": "symbol", "config": { "label": "Heading", "required": true } },
"image": { "type": "media", "config": { "label": "Image" } }
}
}
}
}
}
}'
Edit them
In the entry editor a Blocks field shows one card per block. Add a block from the menu of types you defined, drag cards to reorder, collapse the ones you are not working on, and remove any you no longer need. Media and rich text work inside blocks exactly as they do anywhere else.
When something is missing, publish tells you exactly where: "Sections, block 2 (Hero): Heading is required." Over the API the same error comes back keyed by position, as sections.1.data.heading.
Write them
A blocks value is an ordered list of { type, data }. Each block is delivered with an id your front end can use as a key. Blocks added in the editor keep theirs for good; when writing over the API, send your own id to keep it stable across edits.
{
"title": "Home",
"sections": [
{ "type": "hero", "data": { "heading": "Welcome", "image": { "asset_id": 12, "fit": "cover" } } },
{ "type": "quote", "data": { "text": "It just works.", "author": "Sam" } }
]
}
Render them
The delivery API returns each block as { id, type, data }, with media expanded to full objects and rich text in its delivered shape. In React, SliceZone maps each block type to a component:
import { SliceZone, type BlockComponents } from '@mojimoto/react';
const components: BlockComponents = {
hero: ({ slice }) => <h1>{slice.data.heading as string}</h1>,
quote: ({ slice }) => <blockquote>{slice.data.text as string}</blockquote>,
};
export function Page({ page }) {
return <SliceZone slices={page.data.sections} components={components} />;
}
Generated TypeScript types include a type for each block, so slice.data is fully typed once you run npx @mojimoto/cli types.
Design content types with typed fields, reusable components, and references - a model your front end can rely on.
Download TypeScript types for your content model so your editor autocompletes fields and your build catches mismatches.
Set up Claude Code, Cursor or Codex with one command: a Mojimoto skill, your real content model and the MCP server, with no token written to disk.