Skip to main content

Workflows

Workflows replace the simple draft / published toggle with an ordered, configurable state machine per content type. Types without an assigned workflow keep the simple flow — fully backward-compatible.

State model

A workflow is an ordered array of states. Each state has:

{
key: string; // machine-readable, e.g. "in-review"
label: string; // display name
color: string; // #RRGGBB for badges
isInitial?: boolean; // exactly one — where new entries start
isFinal?: boolean; // exactly one — the publish destination
assignable?: boolean; // can have an assignee at this state
}

Transitions are linear: forward = next index, backward = previous. The final state acts as "published" — reaching it triggers publishEntryLocale (RouteIndex update, webhooks, cache invalidation). Leaving the final state backward unpublishes.

Default editorial workflow

The starter pattern:

[
{ "key": "draft", "label": "Draft", "color": "#9CA3AF", "isInitial": true },
{ "key": "in-review", "label": "In Review", "color": "#F59E0B", "assignable": true },
{ "key": "approved", "label": "Approved", "color": "#10B981" },
{ "key": "published", "label": "Published", "color": "#22C55E", "isFinal": true }
]

Assigning a workflow to a content type

Edit the content type. Workflow dropdown picks any active workflow definition. Setting it to "None" reverts the type to simple draft / published.

PUT /api/v1/projects/demo/content-types/articlePage
{ "workflowSlug": "editorial" }

Transitions

When an entry's content type has a workflow, the entry editor replaces Publish / Unpublish with transition buttons sourced from availableTransitions(states, currentState). Forward goes to the next state; backward to the previous.

POST /api/v1/projects/demo/entries/{id}/workflow
{ "locale": "en-US", "direction": "forward", "note": "ready for review" }

Permissions:

  • Forward to the final state needs publish.
  • Everything else needs update on the content type.

Per-locale independence

Each locale progresses through the workflow independently. English can be at "Published" while French is at "In Review" and Spanish is at "Draft".

Assignment

For states with assignable: true, transitions can attach an assignee. The transition record (WorkflowAssignment) is the source of truth for "who's on this":

{
entryId: string;
locale: string;
state: string; // workflow state key
assigneeId: string?; // null when unassigned
assignedById: string?;
note: string?;
createdAt: Date;
}

The Translations dashboard surfaces assignees alongside translation status.

CRUD

GET /api/v1/projects/demo/workflows
POST /api/v1/projects/demo/workflows
PUT /api/v1/projects/demo/workflows/{slug}
DELETE /api/v1/projects/demo/workflows/{slug}

PUT refuses to remove a state with live entries in it (state_in_use). DELETE refuses if any content type references the workflow (workflow_in_use).

Admin UI

Settings → Workflows tab. Create / edit / delete with a visual diagram of the state strip, per-state color picker, isInitial / isFinal radios, assignable checkbox, reorder up / down. New-workflow dialog seeds with the default editorial template.

The entry editor renders a WorkflowStatusBar above the action buttons showing the full state strip with the current state highlighted in its color.

Skipped-workflow detection

The governance analyzer flags entries that reached the final state without a WorkflowAssignment row — usually a direct publish that bypassed review. See Multi-tenant / Governance.