Blocks field
Author an ordered, discriminated list of different content shapes.
Use field.Blocks for an ordered list that may contain different authored shapes: a page-builder
layout, email sections, or a portable content stream. Every row stores a stable blockType
discriminator and the fields declared by that block type.
In the admin

Each row can use a different configured shape; blockType preserves its type in stored data and generated unions.
Smallest working example
field.Blocks(
"layout",
field.BlockTypes(
field.BlockType("hero", "Hero",
field.Text("heading", field.Required()),
field.Textarea("summary"),
),
field.BlockType("quote", "Quote",
field.Textarea("text", field.Required()),
field.Text("source"),
),
),
){
"layout": [
{ "blockType": "hero", "heading": "Build with Ridu" },
{ "blockType": "quote", "text": "Content is structured data." }
]
}At least one block type is required. Keys must be unique lowercase kebab-case. A direct block child
cannot be named blockType because Ridu owns that discriminator.
Contracts, queries, and authoring
Generated TypeScript exposes a discriminated union, so narrowing on blockType gives the exact
fields for that row. Authors choose a block type, edit it, reorder the list, and may use a paired
RowLabelComponent for richer row headings.
Nested query paths include the block key, for example layout.quote.source; the container supports
exists. Put Localized on a child for translated values within a shared layout or on Blocks when
each locale owns its complete block selection and order.
Common mistakes
- A block key is a persisted API discriminator. Renaming/removing one requires a reviewed data migration and consumer update.
- Use Array when every row has the same shape; it produces a simpler contract.
- Blocks store structured content. Handle every generated union member in the frontend.
See field.Blocks, field.BlockType, and
Generated contracts.