Author content with blocks
A blocks field holds an ordered list of typed components — the way to put richer structure (callouts, galleries, embeds, product cards) inside an article instead of a single prose body. Each block type is a named set of fields, so everything you can do with a top-level field (assets, references, inline asset:// images, validation) works inside a block too.
Use blocks for renderable structured content. Use json only for opaque, non-renderable data (config, metadata) — json is never expanded, never indexed, and not covered by the asset delete guard, and the server rejects asset:// tokens or block-shaped arrays inside it.
1 — Declare block types on the collection
A blocks field carries its allowed block types in options.blockTypes. Each block type has a name (lowercase, the same rules as field names), an optional label, an optional description, and a list of fields (any field types except blocks — blocks cannot nest). Use description to say what the block is for and when to reach for it — e.g. "Highlights a warning the reader must not miss; don't use for ordinary body text" — so the agent picks the right block while authoring.
{
"name": "cms_create_collection",
"arguments": {
"name": "Articles",
"slug": "articles",
"fields": [
{ "name": "title", "type": "text", "required": true },
{ "name": "slug", "type": "text", "required": true },
{
"name": "body",
"type": "blocks",
"options": {
"blockTypes": [
{ "name": "callout", "label": "Callout", "description": "A highlighted box for an important aside or warning the reader must not miss. Not for ordinary body text.", "fields": [
{ "name": "tone", "type": "select", "options": { "choices": ["info", "warn"] } },
{ "name": "text", "type": "markdown" }
]},
{ "name": "gallery", "label": "Gallery", "description": "A set of images shown together as a gallery. Use when you have two or more related photos; for a single image embed it inline instead.", "fields": [
{ "name": "images", "type": "array", "options": { "items": { "name": "img", "type": "asset" } } }
]},
{ "name": "product_card", "label": "Product card", "description": "Promotes one product from the products collection inline in the article.", "fields": [
{ "name": "product", "type": "reference", "options": { "targetCollection": "products" } }
]}
]
}
}
]
}
}
2 — Author block content on an entry
A block instance is { "type", "key", "props" }: type names one of the collection's block types, key is an opaque stable id for your own list rendering (optional — defaulted if omitted), and props holds the values keyed by that block type's field names.
{
"name": "cms_create_entry",
"arguments": {
"collection": "articles",
"slug": "spring-launch",
"data": {
"title": "Spring launch",
"slug": "spring-launch",
"body": [
{ "type": "callout", "key": "c1", "props": { "tone": "info", "text": "Now shipping! " } },
{ "type": "gallery", "key": "g1", "props": { "images": ["<assetId>", "<assetId2>"] } },
{ "type": "product_card", "key": "p1", "props": { "product": "<productEntryId>" } }
]
},
"status": "draft"
}
}
props are validated against the block type's fields. An unknown block type, a missing/non-object props, or a bad prop value fails with cms_invalid (a clean 400) — fix the block and retry. Inline asset://<id> tokens inside a block's markdown/rich_text prop must reference an already-uploaded asset (see skill://cms/upload-asset-and-embed); an unknown inline asset is rejected on write.
3 — Reading blocks back
On the delivery API and cms_get_entry:
- Typed
assetprops (andarray<asset>) expand to the full asset object (publicUrl,altText,mime,sizeBytes). - Inline
asset://<id>tokens in block prose are rewritten to the asset'spublicUrl, and an_assetsmap (keyed by asset id) is attached alongsidedataforaltText/dimensions. referenceprops stay raw ids by default. Request expansion explicitly: deliveryGET /v1/cms/<org>/articles/<slug>?include=references, orcms_get_entry/cms_list_entrieswith"include": ["references"]. Expanded references resolve one level to{ id, slug, collection, locale, data }; references inside the referenced entry are not followed.
Linking to another entry inline (ref://)
To link or embed another entry from within prose (a markdown/rich_text field or block prop), write a ref://<entryId> token — e.g. see [our pricing](ref://ent_pricing). Unlike asset:// (which is rewritten to a URL on read), a ref:// token is left in place, because the server doesn't know your site's routing. Instead, under ?include=references the response carries a _refs map keyed by entry id → { id, slug, collection, locale, data }; your renderer detects ref://<id>, looks it up in _refs, and builds its own link (/blog/<slug>) or embed. Tokens whose target isn't published (or doesn't exist) simply have no _refs entry.
What NOT to do
- Don't nest blocks. A block type's fields cannot include another
blocksfield. - Don't put renderable content in
json. Assets, references, andasset://tokens injsonare invisible to expansion and the delete guard; the server rejects the obvious cases. Useblocks(or a typedasset/referencefield) instead. - Don't reference an asset before it is uploaded. Inline tokens in block prose are validated on write.
Related
skill://cms/upload-asset-and-embed— upload an asset and get the id you embed in a block.skill://cms/publish-entry— the update + publish dance once the block content is ready.skill://cms/design-collection— choosing field types when you design the collection.