Documentation

Revisions

Add revision history with restore and version checks against overwrites to a VitNode Content Engine content type with the editorial option.

The editorial block keeps a history of every save that staff can restore, and stops two people from overwriting each other. It is also what previews, scheduling and live editing build on. The examples use newsroom.story from the newsroom plugin.

Turn on revisions

Add editorial to the definition. true keeps the newest 50 revisions per record. Set revisions.retention for another number, up to 500:

plugins/newsroom/src/content/story.ts (excerpt)
export const storyContentType = defineContentType({
  id: "newsroom.story",
  // ...
  editorial: {
    revisions: { retention: 20 },
    // ...
  },
});

Then migrate from the workspace root:

Migrate the database
bun run db:migrate

The migration adds a version column. Revisions, schedules, drafts and locks live in shared core tables, so nothing else lands in your schema. Editorial also adds a can_restore staff permission. Label it with the newsroom:story:can_restore key in your plugin's locale file. Every option and default is in the content type reference.

Revisions

Every create, update, publish, unpublish and restore stores a snapshot in core_content_revisions, and so does every hide and unhide with visibility. revisions.retention keeps the newest snapshots per record, 50 by default and at most 500.

To see them, open the row's More actions menu in the AdminCP list and choose History. Each version shows who saved it and when, and Show changes lists the fields it changed:

History of this Story dialog: v3 by Test Admin expanded to show the title change from Spring menu launch to Spring menu launches Monday and Featured switched on, with Restore buttons on v2 and v1

Restore puts that version's field values back as a new version, so nothing in between is lost. The publication status and the hidden state stay as they are. It needs can_restore and emits content.newsroom.story.restored. Anyone with the record open in live editing gets a notice and a reloaded form.

Safe concurrent edits

Each editorial write raises the record's version by one and succeeds only when the version still matches the one the editor loaded. If someone saved in between, the second save fails instead of quietly replacing their work:

PUT /api/newsroom/admin/content/story/2 → 409
{
  "code": "CONTENT_VERSION_CONFLICT",
  "contentTypeId": "newsroom.story",
  "itemId": 2,
  "expectedVersion": 2,
  "currentVersion": 3
}

The AdminCP sends the version for you. Your own calls to the edit routes must send it too: PUT /{id} takes { "expectedVersion": 3, "values": { "title": "…" } } and DELETE /{id} takes { "expectedVersion": 3 }. In server code, storyContent.editorialService does the same check, as Save like the AdminCP shows. A translation has its own version and fails with CONTENT_TRANSLATION_VERSION_CONFLICT.