⚠️ Work in progress — createCMS is pre-1.0 and not production-ready (not tested in production). Expect breaking changes.
Reference
Error codes
The CMS error codes, and how to detect them.
The CMS throws typed errors (CMSError, which extends better-call's APIError). Each has a stable code, an HTTP status, and a default message. Codes are defined in CMS_ERRORS (see packages/cms/src/core/errors.ts). Plugins add their own codes, merged into cms.$ERROR_CODES.
import { isCMSError, getCMSErrorCode } from '@createcms/core';try { await cms.api.pages.publishBranch({ body: { rootId, branchId } });} catch (err) { if (isCMSError(err, 'PUBLICATION_APPROVAL_REQUIRED')) { // handle this specific case } const code = getCMSErrorCode(err); // CMSErrorCode | undefined}
Function
Signature
Purpose
isCMSError
(error, code?) => error is CMSAPIError
Narrow an unknown error, optionally to one code.
getCMSErrorCode
(error) => CMSErrorCode | undefined
Read the code off an error.
CMSError
new CMSError(code, overrides?)
Construct or throw a CMS error by code.
These helpers work server-side, where errors are APIError / CMSError. The browser client throws a CMSClientError (a plain Error, not APIError), which isCMSError / getCMSErrorCode do not recognize. Detect it by its cmsCode:
import { CMSClientError } from '@createcms/core';try { await cmsClient.pages.publishBranch({ body: { rootId, branchId } });} catch (err) { if (err instanceof CMSClientError && err.cmsCode === 'PUBLICATION_APPROVAL_REQUIRED') { // handle this specific case }}
Block type is not allowed inside the target parent (violates the collection's structure rules or the parent's allowChildren)
PROTECTED_BRANCH
403
A direct content mutation was attempted on a published branch while branchProtection.protectPublishedBranches is enabled (edit via a separate branch + merge, or unpublish first)
COMMIT_MESSAGE_REQUIRED
400
A content mutation was made with no message while forceCommitMessage is enabled
COMMIT_NOT_FOUND
404
Commit not found
HEAD_MISMATCH
409
The branch advanced past the expectedHeadCommitId you passed — somebody else committed in between. Reload the tree and retry (optimistic concurrency; only thrown when you opt in by sending the field)
EMPTY_SNAPSHOT
400
Empty snapshot — no versions found
BLOCK_ALREADY_DELETED
400
Block is already deleted
TYPE_MISMATCH
400
Block type does not match the expected type
ROOT_HAS_CHILDREN
409
Cannot delete a root that has child roots; archive or move the children first
ROOT_IN_USE
409
Cannot delete: this root is embedded as a reusable block on live roots; remove those references first
CANNOT_MOVE_ROOT
400
Cannot move the root block
CANNOT_MOVE_INTO_SELF
400
Cannot move an item into itself
CANNOT_MOVE_INTO_DESCENDANT
400
Cannot move an item into its own descendant
CIRCULAR_REFERENCE
400
Cannot move a root under itself or one of its descendants
PARENT_ROOT_NOT_FOUND
404
Parent root not found in this collection
REFERENCE_DEPTH_EXCEEDED
422
Reference nesting is too deep (a reusable block embeds others past the limit)
INVALID_REFERENCE
400
A written image or reference property points at an asset or entry id that does not exist
MISSING_TARGET_PROPERTIES
400
targetProperties is required when duplicating a root
DUPLICATE_BLOCK_REQUIRES_PARENT
400
duplicateBlock was called without targetParentBlockId — use duplicateRoot to duplicate an entry
An approval has already been requested from this reviewer
APPROVAL_NOT_PENDING
400
Approval is not pending
APPROVAL_REVIEWER_MISMATCH
403
Only the requested reviewer can approve or reject this request
APPROVAL_STALE
400
Approval is stale: the branch has advanced past the approved commit
APPROVALS_STALE
409
Thrown by executeMerge when branchProtection.dismissStaleApprovals is on and the source branch gained commits after it was approved — request approval again. (The singular APPROVAL_STALE above is about one approval record; this one is the merge gate.)
MERGE_APPROVAL_REQUIRED
400
Cannot merge: approval is required before execution
PUBLICATION_APPROVAL_REQUIRED
400
Cannot publish: approval is required before publication
APPROVALS_NOT_FULLY_APPROVED
400
Cannot proceed: not all requested approvals are approved
Multiple roots match this slug — use rootId for an unambiguous lookup
SLUG_ALREADY_EXISTS
409
A root with this slug on this collection with this parentRootId already exists
PUBLISH_SLUG_CONFLICT
409
Publish-time slug uniqueness: another live entry in this scope already uses the slug. Drafts may hold colliding slugs; a published one must be unique. Inside a release, this rolls the whole atomic publish back
SLUG_NOT_ENABLED
400
This collection does not have slugs enabled
SLUG_EMPTY_NOT_ALLOWED
400
Empty slug is not allowed for this collection (allowIndex is false)
SLUG_GENERATION_FAILED
500
Failed to generate a unique slug after maximum attempts
NESTING_NOT_ENABLED
400
parentRootId is not allowed — this collection does not have nested roots enabled
REDIRECT_NOT_FOUND
404
Redirect not found
REDIRECT_INVALID
400
A redirect endpoint must be a page (rootId) or a path, matching its type
Request body/query failed schema validation (the response body includes an issues array).
Unlike the codes above, VALIDATION_ERROR is not in CMS_ERRORS — it is produced by the framework (better-call) whenever a request fails its Zod schema, and its response body carries the Zod issues array describing what failed.
Plugins add their own codes (for example TENANT_SLUG_REQUIRED, AB_TEST_NOT_FOUND). See each plugin page.