Noma

Versions

Versions

Every time a content entry is published, Noma writes an immutable snapshot of that entry to a content_entry_versions table. The row in content_entries keeps being the working draft; published reads are served from the latest snapshot.

This page documents the public Content API endpoints for listing, inspecting, reverting, and labelling versions.

Model

  • content_entries is the working draft. It holds live field values, and may diverge from the last published snapshot.
  • content_entry_versions stores one row per publish. Each row has a monotonic version_number (per-entry), a JSON snapshot, a locale, a translation_group_id, and optional label / description.
  • The entry's published_version_id / published_version_number point at the currently live version, or are null when the entry is in draft state (or has never been published).
  • is_draft_dirty is true when the working draft has been edited since the last publish.

Versions are per entry, not per translation group. Each locale has its own version history.

What triggers a new version?

Saves never mint versions or change publish state — they only mutate the working draft. Versions are created only by explicit publish actions (or revert, which re-publishes a prior snapshot).

ActionResult
POST /api/{collection}/{uuid}/publish (see Publish)New version v{N+1}, is_draft_dirty = false, state = 'published'
POST /api/{collection}/{uuid}/unpublish (see Unpublish)state = 'draft', published_version_id cleared, versions retained
PUT / PATCH on an entry (draft or published)Fields updated, is_draft_dirty = true, no new version, published_version_id unchanged
Create an entry with state: 'published'New version v1, is_draft_dirty = false (publish-on-create convenience)
Revert to a prior versionNew version minted from that snapshot (vNext), is_draft_dirty = false

Retention

The number of versions retained per entry is capped by your plan:

PlanVersions per entry
Basic10
Grow50
ProUnlimited

When the cap is reached, the oldest versions are pruned on the next publish. The currently published version is never pruned.

Draft vs. Published reads

The existing Get an Entry and List Entries endpoints decide their data source from the state query parameter:

  • state=published (default) — serves the published snapshot for each entry. Entries that have never been published are excluded (404 on get, filtered out on list).
  • state=draft — serves the live working-draft field values from content_entries.

This means a published entry is stable: editing the draft does not change what published consumers see until you publish again.

List versions

GET /api/{collection}/{uuid}/versions HTTP/1.1
Host: app.nomacms.com
project-id: <project-uuid>
Authorization: Bearer <api-token>
Accept: application/json

Requires the read ability on the token.

Path parameters

ParamDescription
collectionCollection slug
uuidContent entry UUID

Response (200)

JSON object with a data array of versions, ordered by version_number descending. Each item has this shape:

FieldTypeDescription
uuidstringVersion UUID
version_numberintegerMonotonic per-entry version counter (1, 2, 3, …)
labelstring | nullOptional short label
descriptionstring | nullOptional notes
localestringLocale of the snapshot
translation_group_idinteger | nullTranslation group of the entry at publish time
published_atstring | nullISO 8601 timestamp
created_atstringISO 8601 timestamp
updated_atstringISO 8601 timestamp
created_byobject | null{ id, name } when available
is_current_publishedbooleantrue when this is the entry's currently published version

The snapshot payload is not included on list.

Get a version

GET /api/{collection}/{uuid}/versions/{version} HTTP/1.1
Host: app.nomacms.com
project-id: <project-uuid>
Authorization: Bearer <api-token>
Accept: application/json

Requires the read ability on the token. {version} is the integer version_number.

Response (200)

Same shape as the list item, plus a snapshot object:

{
  "uuid": "",
  "version_number": 3,
  "label": "Launch copy",
  "description": "Approved by marketing",
  "locale": "en",
  "translation_group_id": 42,
  "published_at": "2026-04-20T10:15:00Z",
  "created_at": "2026-04-20T10:15:00Z",
  "updated_at": "2026-04-20T10:15:00Z",
  "created_by": { "id": 7, "name": "Alex" },
  "is_current_published": true,
  "snapshot": {
    "fields": {
      "title": "Hello World",
      "cover": "a1b2c3d4-…",
      "tags": ["news", "launch"]
    },
    "meta": {
      "locale": "en",
      "translation_group_id": 42
    }
  }
}

The snapshot.fields object mirrors the same shape accepted on create/update: scalars for primitive fields, UUID arrays for media fields, UUID or ID arrays for relation fields, and nested arrays for group fields. This keeps reverts and inspections symmetric with the write API.

Revert to a version

POST /api/{collection}/{uuid}/versions/{version}/revert HTTP/1.1
Host: app.nomacms.com
project-id: <project-uuid>
Authorization: Bearer <api-token>
Accept: application/json

Requires the update ability on the token.

Reverting restores the working draft from the target snapshot and publishes it as a brand new version (vNext). The old versions are preserved (subject to retention). After revert, is_draft_dirty becomes false and published_version_number points at the new version.

Response (200)

{
  "message": "Reverted to version 3 (new version v7).",
  "version": { "version_number": 7, "": "" },
  "entry": {
    "uuid": "",
    "fields": { "title": "Hello World", "": "" }
  }
}

Missing media or relation targets (for example, an asset deleted after the snapshot was taken) are skipped when the draft is restored; the snapshot itself is left untouched.

Update version metadata

Labels and descriptions are editable, but the snapshot payload itself is immutable.

PATCH /api/{collection}/{uuid}/versions/{version} HTTP/1.1
Host: app.nomacms.com
project-id: <project-uuid>
Authorization: Bearer <api-token>
Accept: application/json
Content-Type: application/json

Requires the update ability on the token.

JSON body

FieldTypeDescription
labelstring | nullMax 255 chars
descriptionstring | nullMax 2000 chars

Response (200)

The updated version resource (without snapshot).

Errors

StatusWhen
400Missing project-id or project cannot be resolved
401Missing or invalid bearer token
403Token does not have the required ability
404Collection, entry, or version not found

Examples

import { createClient } from "@nomacms/js-sdk"
 
const client = createClient({
  projectId: process.env.NOMA_PROJECT_ID!,
  apiKey: process.env.NOMA_API_KEY!,
})
 
const versions = await client.content.versions.list("blog-posts", entryUuid)
const v3 = await client.content.versions.get("blog-posts", entryUuid, 3)
 
await client.content.versions.updateLabel("blog-posts", entryUuid, 3, {
  label: "Launch copy",
  description: "Approved by marketing.",
})
 
await client.content.versions.revert("blog-posts", entryUuid, 3)
curl -sS "https://app.nomacms.com/api/blog-posts/${ENTRY_UUID}/versions" \
  -H "project-id: $NOMA_PROJECT_ID" \
  -H "Authorization: Bearer $NOMA_API_KEY" \
  -H "Accept: application/json"
 
curl -sS "https://app.nomacms.com/api/blog-posts/${ENTRY_UUID}/versions/3" \
  -H "project-id: $NOMA_PROJECT_ID" \
  -H "Authorization: Bearer $NOMA_API_KEY" \
  -H "Accept: application/json"
 
curl -sS -X POST "https://app.nomacms.com/api/blog-posts/${ENTRY_UUID}/versions/3/revert" \
  -H "project-id: $NOMA_PROJECT_ID" \
  -H "Authorization: Bearer $NOMA_API_KEY" \
  -H "Accept: application/json"
 
curl -sS -X PATCH "https://app.nomacms.com/api/blog-posts/${ENTRY_UUID}/versions/3" \
  -H "project-id: $NOMA_PROJECT_ID" \
  -H "Authorization: Bearer $NOMA_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"label":"Launch copy","description":"Approved by marketing."}'
  • Get an Entry - state=published returns the latest snapshot
  • List Entries - state=published filters out never-published entries
  • JavaScript SDK - Content - client.content.versions.*
  • MCP Server - Available Tools - list_entry_versions, get_entry_version, revert_entry_version, update_entry_version_label

Search documentation

Find guides and reference pages