Versioning and approval are properties of BaseModel, not of any one feature — they work identically on a page, a product, a payroll run and a community post. Publishing applies to pages and posts: for any other datatype publish and unpublish do nothing.
States
ModelState values, in the order they normally occur:
draft → new → pending → inprogress → reviewed → approved → published → completed
Off-ramps: hold, rejected, cancelled, archived, deleted.
Filter by state on any list or search call using options.modelState, which accepts one state or an array — this is how a public site fetches only published records.
Approval
/repository/request-approval/:datatype/:idJWT/repository/approve/:datatype/:idJWT/repository/reject/:idJWTWho may approve is governed by the review and approve content permissions — held through roles such as Reviewer and Publisher, or granted per record through requiredRole.
Publishing
/repository/publish/:datatype/:idJWT/repository/unpublish/:datatype/:idJWTPublishing copies the page or post into the published collection — same sk, with sourceDatatype naming the original — and marks the source published with a publishedDate. Sites read that copy (for posts always; for pages when the site sets publishedPagesOnly).
Any later save marks the source draft again — unpublished changes — while the live copy stays as it was until you publish again. Unpublishing deletes the published copy and resets the source to draft with no publishedDate; the source keeps its content. Deleting the source deletes its published copy too.
History
/repository/history/:datatype/:idJWT/repository/history/:restoreJWTversion increments on each write and prior revisions are retained in the history datatype. HistoryModule (/history/*) exposes the same data as its own surface.
Trash
Deletes are soft. Records move to the trash datatype and can be brought back:
/repository/trash-restoreJWTDELETE /repository/truncate/:datatype is the exception — it empties the collection, with nothing to restore. It is a root-admin API call only; the Studio does not offer it.
Per-record permissions
requiredRole stores content permissions per operation on the record itself:
{
"requiredRole": {
"read": ["read"],
"update": ["update", "approve"],
"delete": ["delete"]
}
}Enforced in two places:
JwtAuthGuardreadsrequiredRole.updateandrequiredRole.deletebefore the handler runs.ContentPermissionInterceptorreadsrequiredRole.read— and falls back torequiredRole.review— on the response, throwing403after the handler has already executed.
A user-datatype caller is allowed through when requiredRole.read includes User, without any permission comparison.
For a list, the interceptor inspects data[0] and applies its requiredRole to the entire response. A list mixing records with different read requirements is not filtered element by element — either all of it returns or the whole call is 403.
Workflow and notes
workflow on a record holds a TaskModel, connecting it to WorkflowModule (/workflow) and the task datatype.
notes is an array of { author, comment, date } on any record, with its own surface:
/notes/:datatype/:idJWT/notes/:datatype/:idJWTCommentsModule (/comments) is the heavier sibling — threaded discussion with moderation, mentions and edits — where notes is deliberately just inline commentary.
Activity
Significant events are written as activity records via recordActivity(orgId, datatype, id, actor, { type, status, comment, meta }). Sign-in writes one on both success and failure, which is what makes the login audit trail work without a separate audit system.