⚠️ Work in progress — createCMS is pre-1.0 and not production-ready (not tested in production). Expect breaking changes.
createCMS
Reference

Merges API

Diff branches, open merge requests, resolve conflicts, and merge.

Merge methods diff two refs of an entry, open and manage merge requests, resolve conflicts, and integrate one branch into another. They live at cms.api.<collection>.<method> and mirror on the client with identical types (the examples below use pages as the sample collection). Read methods take a query object, write methods take a body object.

Every commit-producing mutation returns commit: { id, message, createdAt, createdBy }, the new head after the change.

Diff Two Refs

Diff two refs of one entry to see exactly what changed between them, as a flat change list, an annotated render tree, or both. Each side is a branch (resolved to its head commit) or a commit, and the target can also be the entry's current publication, so you can preview precisely which draft edits are not yet live. The base is the common ancestor of the two resolved commits.

mergeRequest:read
GET/{collection}/getDiff
const { data, error } = await client.pages.getDiff({
  query: {
    sourceBranchId: 'br_draft', // one source ref: sourceBranchId XOR sourceCommitId
    targetBranchId: 'br_main', // one target ref: targetBranchId, targetCommitId, or targetPublished
  },
});
Parameters
sourceBranchIdstring

Source branch ref. Provide exactly one of sourceBranchId or sourceCommitId.

sourceCommitIdstring

Source commit ref. Provide exactly one of sourceBranchId or sourceCommitId.

targetBranchIdstring

Target branch ref. Provide exactly one of targetBranchId, targetCommitId, or targetPublished.

targetCommitIdstring

Target commit ref. Provide exactly one of targetBranchId, targetCommitId, or targetPublished.

targetPublishedboolean

Diff against the source root's current publication (the published branch's live head).

view'list' | 'tree' | 'both'= 'both'

Which representations to return. `diff` is null when 'tree', `tree` is null when 'list'.

withAttributionboolean= false

Attach per-entry attribution (commit id, timestamp, author) to each change.

Returns
diffBlockChange[] | null

The flat change list (see The diff shape below). `null` when `view` is `tree`.

treeAnnotatedBlockTreeNode | null

The annotated draft tree. `null` when `view` is `list`, and when the source ref deleted the entry root block.

summaryDiffSummary

Per-changeType entry counts: `{ added, deleted, modified, moved, reordered }` (all numbers).

sourceCommitIdstring

The commit the source ref resolved to.

targetCommitIdstring

The commit the target ref resolved to.

commonAncestorCommitIdstring

The common ancestor of the two resolved commits (the diff base).

Both refs must belong to the same entry (BRANCHES_NOT_SAME_ROOT otherwise). The base is the common ancestor of the two resolved commits; when one commit is an ancestor of the other, the common ancestor is that commit and the diff degenerates to an exact two-way comparison (a branch against targetPublished yields exactly the draft edits that are not live yet, the publish preview).

The diff shape

  • diff (with view: 'list' or 'both', otherwise null): the flat change list. Each entry carries blockId, changeTypes (added, deleted, modified, moved, childrenReordered), propertyChanges when modified (per-property path, kind, from, to, plus word-level textDiff segments for richText properties), typeChange when the block type changed, slugChange on the root entry when the draft slug changed, moved (kind: 'reparented' | 'reordered' with old and new parent id and index), and the full sourceVersion / targetVersion / baseVersion payloads. moved marks only blocks that actually moved (siblings whose index merely shifted are not marked), and childrenReordered only parents whose surviving children truly changed relative order. A slug-only change is not modified: it surfaces as a root entry with an empty changeTypes array and only slugChange set.
  • tree (with view: 'tree' or 'both'): the annotated draft tree, the source branch's block tree where changed nodes carry a diff annotation and deleted blocks are re-inserted as ghost nodes at their old position. It is structurally a regular block tree, so it renders through the same component maps. null when the view is 'list', and also when the draft deleted the entry's root block (guard before rendering).
  • summary: per-changeType entry counts { added, deleted, modified, moved, reordered }.

With withAttribution: true, each diff entry and each tree annotation additionally carries attribution: { commitId, changedAt, changedBy, changedByUser? } — which commit made the change, and who. changedByUser is present only when called with query.withUser, limited to your user config's exposeColumns allowlist. See Review changes visually for rendering the annotated tree.

Check for Conflicts

Check whether two branches would collide before you open a merge request, using a three-way merge (base, source, target). You get back the conflicting blocks and a hasConflicts flag, so you can surface trouble to the author up front.

mergeRequest:read
GET/{collection}/checkConflicts
const { data, error } = await client.pages.checkConflicts({
  query: {
    sourceBranchId: 'br_draft', // required
    targetBranchId: 'br_main', // required
  },
});
Parameters
sourceBranchIdstringrequired

Source branch id.

targetBranchIdstringrequired

Target branch id.

Returns
hasConflictsboolean

Whether any block conflicts between the two branches.

conflicts{ blockId, sourceVersionId, targetVersionId, baseVersionId }[]

One entry per conflicting block. Each version id is `null` when that side has no version for the block.

commonAncestorCommitIdstring

The common ancestor of the two branch heads (the three-way base).

sourceCommitIdstring

The source branch's head commit.

targetCommitIdstring

The target branch's head commit.

Open a Merge Request

Open a merge request to merge the source branch into the target branch. You get the new request back together with any conflicts detected at creation time, so reviewers see them straight away. Fails if an open merge request already exists for this source-target pair.

mergeRequest:create
POST/{collection}/createMergeRequest
const { data, error } = await client.pages.createMergeRequest({
  body: {
    sourceBranchId: 'br_draft', // required
    targetBranchId: 'br_main', // required
    title: 'Publish homepage refresh', // required
  },
});
Parameters
sourceBranchIdstringrequired

Source branch to merge from.

targetBranchIdstringrequired

Target branch to merge into.

titlestringrequired

Merge request title (at least 1 character).

descriptionstring

Longer description.

createdBystring

Explicit actor id, used only when the request has no session user (context takes precedence).

Returns
mergeRequestMergeRequest

The newly created merge request row: id, root id, source/target branch ids, status, title, commit ids, and timestamps.

hasConflictsboolean

Whether conflicts were detected at creation time.

conflicts{ blockId, sourceVersionId, targetVersionId, baseVersionId }[]

One entry per conflicting block, persisted against the merge request.

List Merge Requests

List the merge requests in a collection, filtering by status, branch, or creator and sorting however you need. All query fields are optional, and requests whose entry was archived are excluded.

mergeRequest:read
GET/{collection}/listMergeRequests
const { data, error } = await client.pages.listMergeRequests({
  query: {
    status: 'open',
    limit: 20,
  },
});
Parameters
limitnumber= 20

Page size, 1 to 100.

offsetnumber= 0

Rows to skip.

rootIdstring

Filter by root id.

sourceBranchIdstring

Filter by source branch.

targetBranchIdstring

Filter by target branch.

status'open' | 'merged' | 'closed'

Filter by status.

createdBystring

Filter by creator.

searchstring

Substring match over title and description.

sortBy'createdAt' | 'updatedAt' | 'status' | 'title'= 'createdAt'

Sort field.

sortDirection'asc' | 'desc'= 'desc'

Sort order.

Returns
mergeRequestsMergeRequestListItem[]

The merge requests on this page. Each carries branch names, status, conflict and comment counts, and `hasConflicts`.

totalnumber

Total matching merge requests across all pages, ignoring limit/offset.

hasMoreboolean

Whether more merge requests exist after this page.

Update a Merge Request

Update the title and/or description of an open merge request. Passing neither is a no-op that returns the merge request unchanged.

mergeRequest:update
POST/{collection}/updateMergeRequest
const { data, error } = await client.pages.updateMergeRequest({
  body: {
    mergeRequestId: 'mr_8fd21c', // required
    title: 'Updated title',
  },
});
Parameters
mergeRequestIdstringrequired

The merge request id.

titlestring

New title.

descriptionstring

New description.

Returns
mergeRequestMergeRequest

The updated merge request row.

Execute a Merge

Integrate a merge request into its target branch. It fast-forwards when the target has not diverged, otherwise it writes a merge commit; force a merge commit with noFastForward: true or config mergeStrategy: 'merge-commit'. Every conflict must be resolved first, and a pending approval request blocks the merge.

mergeRequest:update
POST/{collection}/executeMerge
const { data, error } = await client.pages.executeMerge({
  body: {
    mergeRequestId: 'mr_8fd21c', // required
  },
});
Parameters
mergeRequestIdstringrequired

The merge request id.

mergedBystring

Explicit actor id, used only when the request has no session user (context takes precedence).

messagestring

Custom merge commit message (auto-generated if omitted).

noFastForwardboolean

Force a merge commit even when a fast-forward is possible (git's --no-ff). `false` forces a fast-forward.

Returns
commitCommit

The commit envelope `{ id, message, createdAt, createdBy }`: the merge commit on a non-fast-forward, or the source head that became the new target head on a fast-forward.

fastForwardboolean

Whether the merge fast-forwarded (no merge commit written).

rootIdstring

The merged entry's id.

targetBranchIdstring

The branch that received the merge.

commit is the merge commit on a non-fast-forward, or the source head that became the new target head on a fast-forward.

Create a Merge Block Version

Create a new block version to resolve a conflict by hand (a third-way resolution). The version is persisted for the conflict and goes live once the merge is executed. Input is a discriminated union over type, one variant per block definition plus 'root'.

mergeRequest:create
POST/{collection}/createMergeBlockVersion
const { data, error } = await client.pages.createMergeBlockVersion({
  body: {
    mergeRequestId: 'mr_8fd21c', // required
    blockId: 'blk_hero', // required
    type: 'hero', // required: a block name from the collection, or 'root'
    properties: { title: 'Resolved heading' }, // required
  },
});
Parameters
mergeRequestIdstringrequired

The merge request id.

blockIdstringrequired

The conflicting block id.

typeblock name | 'root'required

The block's type literal (a block name from the collection, or 'root').

propertiestyped block propertiesrequired

Full properties for the resolved version, typed by `type`.

childrenstring[]

Child block ids for the resolved version.

Returns
blockVersionIdstring

The id of the new block version, referenced later as a `manual` resolution.

Apply Conflict Resolutions

Resolve one or more conflicts at once, choosing which version to keep for each. A 'manual' resolution must reference an existing block version (from createMergeBlockVersion or a preexisting one). You get back the resolved rows and a count of the conflicts still outstanding.

mergeRequest:update
POST/{collection}/applyConflictResolutions
const { data, error } = await client.pages.applyConflictResolutions({
  body: {
    mergeRequestId: 'mr_8fd21c', // required
    resolutions: [
      { conflictId: 'cf_1a2b', resolution: 'source', resolvedBy: 'usr_1' }, // required, at least one
    ],
  },
});
Parameters
mergeRequestIdstringrequired

The merge request id.

resolutions{ conflictId, resolution, resolvedVersionId?, resolvedBy }[]required

At least one resolution. `resolution` is 'source', 'target', or 'manual'; a 'manual' resolution needs `resolvedVersionId`.

Returns
resolvedMergeConflict[]

The updated conflict rows, each with its chosen `resolution`, `resolvedVersionId`, `resolvedBy`, and `resolvedAt`.

remainingUnresolvednumber

How many conflicts in the merge request still have no resolution.

Close a Merge Request

Close an open merge request without merging it. If someone other than the creator closes it, the creator gets notified.

mergeRequest:update
POST/{collection}/closeMergeRequest
const { data, error } = await client.pages.closeMergeRequest({
  body: {
    mergeRequestId: 'mr_8fd21c', // required
    reason: 'No longer needed',
  },
});
Parameters
mergeRequestIdstringrequired

The merge request id.

reasonstring

Optional reason for closing.

Returns
mergeRequestMergeRequest

The updated merge request row, now in `closed` status.

Reopen a Merge Request

Reopen a closed merge request back to open status. Fails if it was already merged, or if an open merge request already exists for the same source-target pair.

mergeRequest:update
POST/{collection}/reopenMergeRequest
const { data, error } = await client.pages.reopenMergeRequest({
  body: {
    mergeRequestId: 'mr_8fd21c', // required
  },
});
Parameters
mergeRequestIdstringrequired

The merge request id.

Returns
mergeRequestMergeRequest

The updated merge request row, now back in `open` status.

On this page