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

Media

Upload, serve, and manage assets via cms.api.media.

Media methods live under cms.api.media. For the model behind them, see the Media concept.

Uploads

createSignedUpload

Returns presigned PUT URLs; the browser uploads each file straight to the bucket. POST, input via body.

Body fieldTypeDescription
files{ name, size, type, variantOf? }[]Files to upload (at least one).
folderIdstringTarget folder (optional).

Returns { assets: { id, slug, objectKey, url, signedUrl, headers }[], expiresAt }. New assets are created private. url is the direct object URL the asset will have once uploaded — for internal/admin display (e.g. a media library); serve assets in content through the public delivery gate. signedUrl is the presigned PUT — PUT each file to it with the returned headers (Content-Type, x-amz-acl: public-read).

uploadAssets

Uploads file bytes through the server. POST, input via body. Same shape as createSignedUpload, but each file carries a buffer: Blob | ArrayBuffer. Returns { assets: { id, slug, objectKey, url }[] }.

Assets

listAssets

GET. Query: folderId?, status? ('private' | 'public'), search?, limit? (default 20), offset?, sortBy? ('createdAt' | 'slug' | 'size'), sortOrder?. Returns { assets, total, hasMore }. Each asset includes a direct url (${publicUrl}/${objectKey}) for internal/admin display such as a media library — no presign or publicUrl lookup needed; serve assets in content through the public delivery gate.

updateAssetStatus

POST, body: { assetIds: string[], status: 'private' | 'public' }. Returns { updated }.

archiveAsset

Soft-deletes assets (and their variants). POST, body: { assetIds: string[] }. Returns { archived, archivedIds, skipped }. Assets used by live content are skipped, not failed.

moveAssets

POST, body: { assetIds: string[], folderId: string | null } (null moves to the root). Returns { moved, movedIds, skipped }. Moves assets between folders; non-existent, out-of-scope, archived, and variant ids are skipped (surfaced in skipped), and a moved asset's variants follow it into the same folder (variants are never moved on their own). Throws FOLDER_NOT_FOUND (unknown/out-of-scope target) or ASSET_NOT_FOUND (no live ids).

replaceAsset

POST, body: { assetId: string, file: { name, size, type, buffer } }. Returns { asset: { id, slug, objectKey, url, mimeType, size } }. Swaps the bytes behind an asset while keeping its id (and folderId / status), so every content reference picks up the new image with no content change — the id-addressed gate re-resolves it. A new slug / object key is minted for a clean cache-bust and the asset's old variants are archived (regenerate them afterward). Server-side and atomic. Throws CANNOT_REPLACE_VARIANT (target is itself a variant), ASSET_NOT_FOUND, FILE_TOO_LARGE / INVALID_FILE_TYPE, or UPLOAD_FAILED (asset left unchanged).

getAssetUsages

GET, query: { assetId }. Returns { pageCount, pages: { rootId, collection, slug, occurrences }[] }.

Folders

listFolders

GET, query: { parentId? }. Returns { folders: { id, name, parentId, createdBy, createdAt }[] } — the direct child folders of parentId, or the root-level folders when parentId is omitted, sorted by name. Navigate the tree level by level (pass a folder's id to get its children).

The mutations below manage the tree:

MethodInputReturns
createFolderbody: { name, parentFolderId? }{ folder }
moveFolderbody: { folderId, newParentFolderId? }{ folder }
deleteFolderbody: { folderId }{ folderId } (fails with FOLDER_HAS_CONTENT)

Public delivery

GET /media/asset/{id} is a public redirect to the asset's object URL, addressed by the asset id (what content stores) — the way to serve assets in content and on public pages. It honors ?format=webp|jpeg|png, ?w=<width> (resolving a pre-uploaded variant, falling back to the original), and ?download. Private assets return ASSET_ACCESS_DENIED. The redirect is short-cached (max-age=300, not immutable), so swapping the object behind the same id propagates automatically.

On this page