Blocks API
Read and edit the block tree inside an entry.
Block methods live at cms.api.<collection>.<method> and read or mutate the block tree of a single entry on a branch. The same methods are available on the client with identical types. Every commit-producing mutation returns commit: { id, message, createdAt, createdBy }, the new branch head.
Read a Block Tree
Load an entry's block tree for editing, at a branch head or a specific commit. You get back the nested tree with each block's typed properties, ready to render in your editor.
block:read/{collection}/getBlockTreeconst data = await cms.api.pages.getBlockTree({
query: {
rootId: 'root_home', // required
branchId: 'br_main', // required
},
});const { data, error } = await client.pages.getBlockTree({
query: {
rootId: 'root_home', // required
branchId: 'br_main', // required
},
});rootIdstringrequiredThe entry.
branchIdstringrequiredBranch to read (resolves the head commit when `commitId` is omitted).
commitIdstringA specific commit to read; defaults to the branch head.
rawbooleanKeep stored values for editing: skip variable and link resolution.
includeReferencePreviewsbooleanAlso return a `references` sidecar (published previews of embedded references).
treeBlockTreeNodeThe nested block tree, root node first. A discriminated union over `type`; the top node is `type: 'root'` and each node carries its typed `properties` and `children`.
reconstructedbooleanTrue when the commit was rebuilt from a partial snapshot.
referencesRecord<string, BlockTreeNode>optionalPublished preview tree of every embedded reference, keyed by its stored reference value. Only when `includeReferencePreviews` is set.
The tree is a discriminated union over type, and the top node is type: 'root'. reconstructed is true when the commit was rebuilt from a partial snapshot. With includeReferencePreviews: true, references is a Record<storedReferenceValue, tree> holding the published render tree of every reference embedded in the entry, resolved through the active scope.
Resolve an Unsaved Tree
Run the same resolution getBlockTree applies to a stored tree — variables substituted, links resolved to their current href, references as a published-preview sidecar or inlined — on a tree you post. Nothing is written: this is how an editor previews its unsaved working copy (live canvas, HTML/PDF preview, e-mail send-preview). The tree is not validated against the schema; unknown block types and undeclared properties pass through untouched.
block:read/{collection}/resolveTreeconst data = await cms.api.pages.resolveTree({
body: {
rootId: 'root_home', // required
branchId: 'br_main', // required
tree, // required: the (edited) tree, root node first
includeReferencePreviews: true,
},
});const { data, error } = await client.pages.resolveTree({
body: {
rootId: 'root_home', // required
branchId: 'br_main', // required
tree, // required: the (edited) tree, root node first
includeReferencePreviews: true,
},
});rootIdstringrequiredThe entry the tree belongs to (scope check only).
branchIdstringrequiredA branch of that entry (must exist; nothing is read from it).
treeblock tree noderequiredThe tree to resolve, root node first: `{ blockId, type, properties, children }` — the shape `getBlockTree` returns, with your unsaved edits applied.
includeReferencePreviewsbooleanAlso return the `references` sidecar (published previews of embedded references).
inlineReferencesbooleanReplace reference values inside `tree` by their resolved published trees, like `getPublishedContent` does.
treeBlockTreeNodeThe posted tree with variables substituted and links resolved. With `inlineReferences` every reference value is replaced by its resolved published tree (the `getPublishedContent` shape).
referencesRecord<string, BlockTreeNode>optionalPublished preview tree of every embedded reference, keyed by its stored reference value. Only when `includeReferencePreviews` is set.
resolveTree never persists anything and creates no commit; save with updateBlocks when you are done. Roots outside your scope (or of another collection) are rejected with ROOT_NOT_FOUND, exactly like getBlockTree.
Add a Block
Add a block as a child of a parent block. The body is a discriminated union over type: each block type accepts its own typed properties. You get back the new commit and the id of the block you just created.
block:create/{collection}/createBlockconst data = await cms.api.pages.createBlock({
body: {
rootId: 'root_home', // required
branchId: 'br_main', // required
parentBlockId: 'root_home', // required (pass the rootId to attach at the top level)
type: 'Hero', // required
properties: { heading: 'Welcome' }, // required
},
});const { data, error } = await client.pages.createBlock({
body: {
rootId: 'root_home', // required
branchId: 'br_main', // required
parentBlockId: 'root_home', // required (pass the rootId to attach at the top level)
type: 'Hero', // required
properties: { heading: 'Welcome' }, // required
},
});rootIdstringrequiredThe entry.
branchIdstringrequiredThe branch.
parentBlockIdstringrequiredBlock to attach the new block under (pass the `rootId` to attach at the top level).
typeblock type namerequiredOne of the collection's block types.
propertiestyped block propertiesrequiredThe block's properties, typed from its block definition.
positionnumberIndex in the parent's children (defaults to append).
messagestringCommit message (defaults to `Add {type} block`).
expectedHeadCommitIdstringOptimistic-concurrency guard: reject with `HEAD_MISMATCH` if the branch head moved.
commitCommitThe new branch head commit: `{ id, message, createdAt, createdBy }`.
blockIdstringThe id of the newly created block.
Update a Block
Edit one block with patch semantics: the fields you send overwrite, omitted fields stay, and a null value deletes a key.
block:update/{collection}/updateBlockconst data = await cms.api.pages.updateBlock({
body: {
rootId: 'root_home', // required
branchId: 'br_main', // required
blockId: 'blk_hero', // required
type: 'Hero', // required (must match the stored type)
properties: { heading: 'Updated heading' }, // required
},
});const { data, error } = await client.pages.updateBlock({
body: {
rootId: 'root_home', // required
branchId: 'br_main', // required
blockId: 'blk_hero', // required
type: 'Hero', // required (must match the stored type)
properties: { heading: 'Updated heading' }, // required
},
});rootIdstringrequiredThe entry.
branchIdstringrequiredThe branch.
blockIdstringrequiredBlock to update.
typeblock type namerequiredThe block's current type (must match the stored type, else `TYPE_MISMATCH`).
propertiespartial block propertiesrequiredProperties to merge. A `null` value deletes that key; omitted keys stay unchanged.
messagestringCommit message (defaults to `Update {type} block {blockId}`).
expectedHeadCommitIdstringOptimistic-concurrency guard: reject with `HEAD_MISMATCH` if the branch head moved.
commitCommitThe new branch head commit: `{ id, message, createdAt, createdBy }`.
Save a Block Tree
Save a whole tree in one commit: createCMS diffs the tree you send against the branch head and creates, updates, and deletes blocks to match. This is the batch save path your editor calls after local edits, instead of one call per change.
block:update/{collection}/updateBlocksconst data = await cms.api.pages.updateBlocks({
body: {
rootId: 'root_home', // required
branchId: 'br_main', // required
tree, // required: the edited tree from getBlockTree, root node included
expectedHeadCommitId: headCommitId,
},
});const { data, error } = await client.pages.updateBlocks({
body: {
rootId: 'root_home', // required
branchId: 'br_main', // required
tree, // required: the edited tree from getBlockTree, root node included
expectedHeadCommitId: headCommitId,
},
});rootIdstringrequiredThe entry.
branchIdstringrequiredThe branch to write.
treeblock tree noderequiredThe desired final tree, root node first: `{ blockId, type, properties, children }` (the shape `getBlockTree` returns). The root node `blockId` must equal `rootId`.
messagestring= 'Batch update'Commit message.
expectedHeadCommitIdstringOptimistic-concurrency guard: reject with `HEAD_MISMATCH` if the branch head moved.
commitCommitThe new branch head commit, or the unchanged head commit when nothing differed.
changedbooleanTrue when a real commit was written; false when the tree already matched the head.
changed is false when the tree already matches the head (no commit is written and the current head commit is returned), and true when a real commit was created. Every created or updated node is validated before the commit (known block type, valid properties, legal placement), so any violation rejects the whole batch with no partial write.
Move a Block
Move a block (and its subtree) to a new parent and/or a new position within a parent's children.
block:update/{collection}/moveBlockconst data = await cms.api.pages.moveBlock({
body: {
rootId: 'root_home', // required
branchId: 'br_main', // required
blockId: 'blk_hero', // required
newParentBlockId: 'blk_section', // required
newIndex: 0, // required
},
});const { data, error } = await client.pages.moveBlock({
body: {
rootId: 'root_home', // required
branchId: 'br_main', // required
blockId: 'blk_hero', // required
newParentBlockId: 'blk_section', // required
newIndex: 0, // required
},
});rootIdstringrequiredThe entry.
branchIdstringrequiredThe branch.
blockIdstringrequiredBlock to move.
newParentBlockIdstringrequiredBlock to move it under.
newIndexnumberrequiredIndex in the new parent's children (integer >= 0, clamped to range).
messagestringCommit message (defaults to `Move block {blockId}`).
expectedHeadCommitIdstringOptimistic-concurrency guard: reject with `HEAD_MISMATCH` if the branch head moved.
commitCommitThe new branch head commit: `{ id, message, createdAt, createdBy }`.
Moving into itself, into one of its own descendants, or moving the root block is rejected (CANNOT_MOVE_INTO_SELF, CANNOT_MOVE_INTO_DESCENDANT, CANNOT_MOVE_ROOT).
Duplicate a Block
Clone a block subtree under an existing parent. targetParentBlockId is required — this endpoint only ever duplicates into a parent. To spin a subtree off into a brand-new top-level entry instead, use duplicateRoot.
block:create/{collection}/duplicateBlockconst data = await cms.api.pages.duplicateBlock({
body: {
rootId: 'root_home', // required
branchId: 'br_main', // required
blockId: 'blk_hero', // required
targetParentBlockId: 'blk_section', // required: duplicate under this parent
},
});const { data, error } = await client.pages.duplicateBlock({
body: {
rootId: 'root_home', // required
branchId: 'br_main', // required
blockId: 'blk_hero', // required
targetParentBlockId: 'blk_section', // required: duplicate under this parent
},
});rootIdstringrequiredSource entry.
branchIdstringrequiredSource branch.
blockIdstringrequiredBlock (and its whole subtree) to duplicate.
targetParentBlockIdstringrequiredParent for the copy.
targetIndexnumberIndex in the target parent's children (integer >= 0).
messagestringCommit message.
mode'child'Always `'child'`: this endpoint only ever duplicates under a parent.
commitCommitThe new branch head commit: `{ id, message, createdAt, createdBy }`.
blockIdstringThe new copy's id.
mode is always 'child'. The response also carries blockId, the new copy's id.
Delete a Block
Soft-delete a block and all its descendants (tombstones), and remove it from its parent's children. You get back the ids of everything that was tombstoned.
block:delete/{collection}/deleteBlockconst data = await cms.api.pages.deleteBlock({
body: {
rootId: 'root_home', // required
branchId: 'br_main', // required
blockId: 'blk_hero', // required
},
});const { data, error } = await client.pages.deleteBlock({
body: {
rootId: 'root_home', // required
branchId: 'br_main', // required
blockId: 'blk_hero', // required
},
});rootIdstringrequiredThe entry.
branchIdstringrequiredThe branch.
blockIdstringrequiredBlock to delete (with all its descendants).
messagestringCommit message (defaults to `Delete block {blockId}`).
expectedHeadCommitIdstringOptimistic-concurrency guard: reject with `HEAD_MISMATCH` if the branch head moved.
commitCommitThe new branch head commit: `{ id, message, createdAt, createdBy }`.
deletedBlockIdsstring[]Every block tombstoned in this commit: the target plus all its descendants.
deletedBlockIds lists every block tombstoned in the commit (the target plus its descendants).
List Reference Usages
List the pages that embed this reusable block, so you can see where it's used before you change or remove it. Under i18n the inspected entry's translation group expands to all sibling entries, so usage is reported across languages.
root:read/{collection}/getReferenceUsagesconst data = await cms.api.pages.getReferenceUsages({
query: { rootId: 'root_home' }, // required
});const { data, error } = await client.pages.getReferenceUsages({
query: { rootId: 'root_home' }, // required
});rootIdstringrequiredRoot id of the reusable block to inspect.
pageCountnumberHow many distinct live pages embed this reusable block.
pages{ rootId, collection, slug, occurrences }[]One entry per host page. Each `occurrences` item is `{ branchId, blockId, propertyKey }`, locating where the block is embedded.
This endpoint checks root:read, not block:read, because it reports usage of an entry across the collection.