Node catalog
Every node type a workflow can use, with its config fields, defaults and output branches.
These are the node types the workflow editor’s palette offers, with the fields its inspector shows
and validates. This page is generated from the catalog itself when the docs are built, so it always
matches what a workflow accepts. Each node’s anchor is its type, for example
#control.approval.
A node’s outputs are the branches it can take. An edge leaves a node from one output (none
means out); the edges on branches a node didn’t take are skipped, along with any node only they
lead to. See Branches and edges.
Field kinds decide how a value is edited and checked:
durationis a string such as"2 days";logicis a JSON Logic expression over the run;listis a non-empty list of strings;contentType,fieldandreleaseare pickers for a content type, one of its fields, or a release;- text fields accept
{{path}}templates. See Expressions and templates.
Every node also takes retries (0 to 10) and a timeout, outside its config. Nodes marked
undo are reversed if the run later fails; nodes marked waits pause the run for a person or
an event.
Triggers
Section titled “Triggers”A workflow has exactly one trigger. It decides when a run starts and for which entry.
Submit for review
trigger.manualStarted by a person from the entry editor, POST …/entries/:id/workflow-runs, or the MCP server.
- Outputs
out
Entry created
trigger.entry_createdStarted when an entry of the workflow’s content types is created.
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
when | logic | Only when. JSON Logic over the event (input) and entry; empty starts a run for every matching entry. The workflow’s content types already narrow it. |
Entry saved
trigger.entry_savedStarted when a draft is saved. The event carries the new version and the changed fields (input.changedFields).
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
when | logic | Only when. JSON Logic over the event (input) and entry; empty starts a run for every matching entry. The workflow’s content types already narrow it. |
Publish requested
trigger.publish_requestedStarted when someone asks to publish an entry. The run decides whether, and when, it is published.
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
when | logic | Only when. JSON Logic over the event (input) and entry; empty starts a run for every matching entry. The workflow’s content types already narrow it. |
Entry published
trigger.entry_publishedStarted after an entry is published.
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
when | logic | Only when. JSON Logic over the event (input) and entry; empty starts a run for every matching entry. The workflow’s content types already narrow it. |
Entry unpublished
trigger.entry_unpublishedStarted after an entry is unpublished.
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
when | logic | Only when. JSON Logic over the event (input) and entry; empty starts a run for every matching entry. The workflow’s content types already narrow it. |
Release published
trigger.release_publishedStarted once for each entry in a release when the release is published.
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
when | logic | Only when. JSON Logic over the event (input) and entry; empty starts a run for every matching entry. The workflow’s content types already narrow it. | |
maxEntries | number | 100 | At most. Runs started per release; the rest are skipped and reported |
Schedule
trigger.scheduleStarted on a cadence for every entry the query matches (freshness reviews, periodic audits).
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
cron (required) | text | 0 9 * * 1 | Cron. Five fields, UTC |
contentType | contentType | Content type. Empty uses the workflow’s content types | |
filter | json | {} | Filter. An entries query filter, e.g. { "fields.section": "news" } |
maxEntries | number | 100 | At most |
Content
Section titled “Content”Content nodes act on the entry through the same operations as the admin and the API, with the same validation and events.
Validate entry
content.validateRuns the content type’s field validation, the same checks publishing applies. Failures are in the output.
- Outputs
validinvalid
Policy check
content.policyChecks editorial rules: required fields, word counts, forbidden terms, image alt text, references, publishing hours.
- Outputs
passfail
| Field | Kind | Default | Notes |
|---|---|---|---|
rules (required) | json | [{"rule":"required_fields","fields":["title"]},{"rule":"has_alt_text"}] | Rules. required_fields, min_words, max_words, forbidden_terms, has_alt_text, min_references, business_hours |
locale | text | Locale. Empty checks whichever locale has a value |
Publish
content.publishPublishes the entry’s current version. Unpublished again if the run later fails, unless it has been republished since.
- Outputs
out - Undoreversed if the run later fails
Unpublish
content.unpublishTakes the entry off delivery. Not undone if the run later fails.
- Outputs
out
Schedule
content.scheduleSchedules the entry to publish or unpublish later. The schedule is cancelled if the run fails.
- Outputs
out - Undoreversed if the run later fails
| Field | Kind | Default | Notes |
|---|---|---|---|
action | select | publish | Action. One of publish, unpublish |
after | duration | 1 day | After |
at | logic | At (expression). Epoch ms or an ISO date; overrides After |
Add to release
content.add_to_releaseAdds the entry to a release. Removed again if the run fails.
- Outputs
out - Undoreversed if the run later fails
| Field | Kind | Default | Notes |
|---|---|---|---|
releaseId (required) | release | Release |
Publish release
content.publish_releasePublishes every entry in a release together.
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
releaseId (required) | release | {{vars.releaseId}} | Release |
Set fields
content.set_fieldsWrites field values (text interpolates {{paths}}). The prior values are restored if the run fails and nobody has edited the entry since.
- Outputs
out - Undoreversed if the run later fails
| Field | Kind | Default | Notes |
|---|---|---|---|
values (required) | json | {} | Values. { fieldApiId: value } |
locale | text | Locale. Empty uses the default locale |
Set tags
content.set_tagsAdds, removes or replaces the entry’s tags. The prior tags return if the run fails.
- Outputs
out - Undoreversed if the run later fails
| Field | Kind | Default | Notes |
|---|---|---|---|
tags (required) | list | ["reviewed"] | Tags |
mode | select | add | Mode. One of add, remove, replace |
Set stage
content.stageRecords the entry’s workflow stage (shown in the editor and returned by GET …/entries/:id/workflow).
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
stepId (required) | text | in_review | Stage id |
name | text | In review | Stage name |
requiredScope | select | content:write | Move here needs. One of content:write, content:publish, content:manage, delivery:read, preview:read, search:read, space:admin, workflow:approve. The scope the legacy transition API requires to move an entry into this stage |
Assign task
content.assignCreates a task on the entry for a person. Resolved if the run fails.
- Outputs
out - Undoreversed if the run later fails
| Field | Kind | Default | Notes |
|---|---|---|---|
assignee (required) | text | editor | Assignee |
title | text | Review “{{ref}}” | Title |
due | duration | Due in |
Comment
content.commentAdds a comment to the entry (text interpolates {{paths}}).
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
body (required) | textarea | Please take a look at “{{ref}}”. | Comment |
Notify
content.notifyEmails people about the entry. Subject and message interpolate {{paths}}; each step sends once.
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
to (required) | list | To. Email addresses ({{paths}} allowed) | |
subject | text | “{{ref}}” needs attention | Subject |
message | textarea | “{{ref}}” needs attention. | Message |
Webhook
content.webhookPosts a JSON payload to a URL. String values interpolate {{paths}}.
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
url (required) | text | URL | |
payload | json | {"entryId":"{{entry.id}}","title":"{{ref}}"} | Payload |
Control
Section titled “Control”Condition
control.conditionBranches on a JSON Logic expression over the run context.
- Outputs
truefalse
| Field | Kind | Default | Notes |
|---|---|---|---|
expression (required) | logic | {"in":["legal",{"var":"entry.tags"}]} | Expression |
Approval
control.approvalWaits for a person holding the required scope to approve or reject. Escalates when the timeout passes.
- Outputs
approvedrejectedescalated - Waitsfor a person or an event
| Field | Kind | Default | Notes |
|---|---|---|---|
title | text | Review “{{ref}}” | Title |
assignee | text | editor | Assignee |
timeout | duration | 2 days | Escalate after |
requiredScope | select | workflow:approve | Who may decide. One of content:write, content:publish, content:manage, delivery:read, preview:read, search:read, space:admin, workflow:approve. workflow:approve lets Approvers and up decide; content:publish keeps it to publishers |
separateDuties | boolean | Separate duties. Whoever started the run can’t decide. Unset follows the space default | |
instructions | textarea | "" | Instructions. Shown in the approvals inbox |
Wait for event
control.wait_eventWaits for an event: one delivered through the API or MCP, or one contentworker emits (entry.saved when the author revises).
- Outputs
receivedtimeout - Waitsfor a person or an event
| Field | Kind | Default | Notes |
|---|---|---|---|
event (required) | text | entry.saved | Event |
timeout | duration | 7 days | Timeout |
Wait
control.waitSleeps for a duration, or until a timestamp computed from the context.
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
duration | duration | 1 day | Duration |
until | logic | Until (expression). Epoch ms; overrides duration |
Set variables
control.setAssigns run variables from JSON Logic expressions.
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
assign | json | {"cycle":{"+":[{"var":"vars.cycle"},1]}} | Assignments. { name: expression } |
Loop
control.loopRepeats its body while the condition holds, up to a limit. Connect the end of the body back to the “next” input.
- Outputs
bodydone - Loopaccepts a
nextback-edge
| Field | Kind | Default | Notes |
|---|---|---|---|
while | logic | true | While |
maxIterations | number | 12 | Max iterations |
Continue as new
control.continue_as_newEnds this run and starts a fresh one on the same workflow, carrying the variables forward.
- Outputsnone: ends the run
End
control.endFinishes the run. “declined” is a policy or approval no; “failed” undoes every compensable step.
- Outputsnone: ends the run
| Field | Kind | Default | Notes |
|---|---|---|---|
outcome | select | completed | Outcome. One of completed, declined, cancelled, failed |
reason | text | "" | Reason |
AI nodes call the same AI features editors use, with the same validation; their results are proposals unless the node is set to apply them.
Classify
ai.classifyAssigns one label from a list (section, audience, intent) into a variable.
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
labels | list | ["news","opinion","feature","how_to"] | Labels |
into | text | category | Store as var |
Agent router
ai.routerAn LLM reads the entry and context, then chooses exactly one route. Each route is an output. Without a model answer it takes the last route.
- Outputsone per configured route
| Field | Kind | Default | Notes |
|---|---|---|---|
instructions | textarea | Decide how this entry should be reviewed. | Instructions |
routes | list | ["publish","needs_review","ask_author"] | Routes |
guards | json | {} | Guards. { route: JSON Logic } checked in code before the model’s choice is taken: a route whose guard fails can’t be chosen |
Summarize
ai.summarizeWrites a short summary of the entry into a variable.
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
into | text | summary | Store as var |
Assess
ai.assessA decision model reads the entry and returns a probability for every option of every question. Branches on one question.
- Outputsone per configured route
| Field | Kind | Default | Notes |
|---|---|---|---|
questions | json | {"stale":{"type":"noul","instructions":"Is this entry out of date: facts, dates, prices or links that no longer hold?"},"audience":{"type":"choice","instructions":"Who is this entry written for?","criteria":{"beginner":"New to the subject","practitioner":"Works with it day to day","expert":"Deep specialist knowledge assumed"}}} | Questions. Question id → { type: noul | choice | score, instructions, criteria }. 1 to 64. |
branchOn | text | stale | Branch on. A yes/no or choice question id. Empty: one output. |
minConfidence | number | 0 | Unsure below. Confidence 0–1. Above 0 adds an “unsure” output. |
state | select | entry_and_context | State. One of entry, entry_and_context |
into | text | assessment | Store as var |
Translate
ai.translateTranslates the entry’s localized fields into another locale and saves them.
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
targetLocale (required) | text | {{vars.locale}} | Into locale |
sourceLocale | text | From locale. Empty uses the default locale |
Autofill field
ai.autofillDrafts one field’s value from the rest of the entry (an excerpt, an SEO title).
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
field (required) | field | Field | |
locale | text | Locale. Empty uses the default locale | |
overwrite | boolean | false | Overwrite. Off leaves a field that already has a value alone |
Suggest tags
ai.suggest_tagsSuggests tags from the space’s taxonomy, into a variable or applied to the entry.
- Outputs
out
| Field | Kind | Default | Notes |
|---|---|---|---|
into | text | suggestedTags | Store as var |
apply | boolean | false | Apply |
Enrich
ai.enrichRuns the enrich agent (SEO fields, summaries, tags). With review, its proposal waits in the approvals inbox.
- Outputsone per configured route
- Waitsfor a person or an event
| Field | Kind | Default | Notes |
|---|---|---|---|
awaitReview | boolean | true | Wait for review. Off applies the proposal at once |
timeout | duration | 7 days | Review within |
Moderate
ai.moderateRuns the moderation agent. Anything it can’t check (no model answer) goes to “flagged”, never “clean”.
- Outputs
cleanflagged
| Field | Kind | Default | Notes |
|---|---|---|---|
instructions | textarea | "" | House rules. Added to the moderation prompt |
into | text | moderation | Store as var |