Workflow

Fire system action by name

Changed on

Fire a default system action by name on a target contentlet.

Returns a map of the resultant contentlet, with an additional AUTO_ASSIGN_WORKFLOW property, which can be referenced by delegate services that handle automatically assigning workflow schemes to content with none.

Use PUT for a single contentlet. This path also accepts POST, but that is a different operation that fires over multiple contentlets and returns a different envelope (entity.results[], a list). Sending a single-contentlet body via POST will not return the created contentlet's identifier where you expect it, even though the record may still be (half-)created by the content type's default workflow — a common silent trap. For one item, always use PUT.

Request body — wrap field values in a contentlet key:

{
  "contentlet": {
    "contentType": "<variable-or-inode>",
    "title": "My New Item",
    "...": "other field values"
  }
}

Field keys inside contentlet are the content type's field variable names (e.g., title, body, image). Unknown field names are silently dropped (a typo like titel will be ignored and may surface as a misleading 'title is required' error). Radio/Select/Checkbox values are not validated against the field's values list — out-of-range values are accepted as-is. Always verify spelling against fields[].variable from GET /api/v1/contenttype/id/{idOrVar}.

Validation error response shape:

{
  "entity": "",
  "errors": [{ "errorCode": "required", "fieldName": "image", "message": "The field Image is required." }],
  "i18nMessagesMap": {}, "messages": [], "pagination": null, "permissions": []
}

errorCode values: required, unknown. fieldName is the field variable for field-specific errors, or null for content-level errors. Note: when the content type is not found, message returns the raw translation key Workflow-does-not-exists-content-type instead of translated text.

Binary and image fields — These fields cannot receive raw file data or asset paths in the JSON body. Use one of the patterns below.

Pattern A — single-use file (works for all binary/image fields):

  1. POST /api/v1/temp (multipart file part) OR POST /api/v1/temp/byUrl (JSON {"remoteUrl":"https://..."}) → use tempFiles[0].id (e.g. "temp_5311313004") as the field value.
  2. Pass that ID in the contentlet body: {"contentlet": {"contentType": "ResortActivities", "image": "temp_5311313004", ...}}.

Pattern B — reusable shared asset (ImmutableImageField only):

  1. Upload via /temp, create a dotAsset contentlet: PUT .../fire/PUBLISH with {"contentlet": {"contentType": "dotAsset", "asset": "temp_<id>"}}.
  2. Use the returned dotAsset identifier as the field value on any ImmutableImageField.
Field clazztemp_<id>dotAsset identifier
ImmutableBinaryField✅❌ (returns 400 "field is required")
ImmutableImageField✅✅

Find a field's clazz by calling GET /api/v1/contenttype/id/{idOrVar} and reading fields[].clazz.

⚠️ Known issue: Firing PUBLISH on an archived contentlet (archived: true) does not validate the archived state and can produce an inconsistent live: true, archived: true tri-state. Always fire UNARCHIVE before PUBLISH on archived content.

⚠️ Multi-scheme content types: When a content type has multiple workflow schemes attached, firing a system action only initializes the contentlet into the scheme whose systemActionMappings entry resolved the fire. Other attached schemes will not have a task for that contentlet, and firing their actions later will fail with 'Workflow Action is not available in the Workflow Step the content is currently in.' To exercise actions in those other schemes, fire by action ID via PUT /api/v1/workflow/actions/{actionId}/fire using an action mapped to the desired scheme.

When chaining workflow actions or reading state back immediately after firing, pass indexPolicy=WAIT_FOR on each call. The default DEFER is asynchronous and can return stale index reads for several seconds, which can mimic server-side state bugs. For isolated one-off fires where nothing reads the result, leave the default.

Block Editor (Story Block) fields: send the value as a Markdown or HTML string — you do not need to hand-author the underlying ProseMirror/JSON document. dotCMS converts it to the Block Editor (ProseMirror JSON) structure automatically on save, so the field reads back as structured content with no editor round-trip required. A value that is already a valid Tiptap/ProseMirror JSON document is detected and stored unchanged. Example: "body": "## Intro\n\nHello **world**.".

Rich blocks in Markdown: blocks that plain Markdown cannot express are written as fenced code blocks whose info string is a dotcms-* label and whose body is a small JSON object. Example — embed a contentlet:

dotcms-content\n{"identifier": "<contentlet-id>", "languageId": 1}\n

Supported labels and payload fields (bold = required):

  • dotcms-content → embedded contentlet: identifier, languageId (default 1). The server rebuilds the full embed data from the identifier on every read.
  • dotcms-image → dotCMS-bound or decorated image: identifier or src; optional alt, title, href, target, textWrap, textAlign, languageId. Plain external images need no fence — standard ![alt](url "title") works.
  • dotcms-video → video: identifier or src; optional mimeType, width, height, languageId.
  • dotcms-youtube → YouTube embed: src; optional start (seconds), width, height.
  • dotcms-grid → layout grid: the verbatim gridBlock node JSON ({"type":"gridBlock","attrs":{"columns":[n,n]},"content":[…two gridColumn nodes…]}).
  • dotcms-node → any other node type verbatim ({"type": "<nodeType>", …}) — the fallback for custom blocks.

Rich blocks in HTML: the same labels are namespaced custom elements. Scalar payloads ride as attributes with hyphenated names (HTML lowercases attribute names, so languageId is spelled language-id, mimeType is mime-type): <dotcms-content identifier="<contentlet-id>" language-id="1"></dotcms-content>. dotcms-ai, dotcms-grid and dotcms-node take the same JSON object as the element's text body instead (HTML-escape < and & inside JSON string values). Always write an explicit closing tag — HTML parsing ignores the / in <dotcms-video … /> and would swallow the content after it. Both carriers produce identical stored documents.

Block styling (e.g. text alignment) is set by an HTML comment on its own line immediately before the block it decorates, in Markdown and HTML alike: <!-- dotcms:attrs {"textAlign":"center"} -->.

A Markdown/HTML write fully replaces the stored document; when stored rich blocks are not carried over in the submitted value, the save still succeeds and an advisory warning listing the replaced blocks is returned in the response messages field — carry the blocks over as fences or elements to preserve them. An invalid payload degrades to an ordinary code block (or is dropped when it carries no text to keep), never an error.

put/api/v1/workflow/actions/default/fire/{systemAction}

Request

Path parameters

systemAction'NEW' | 'EDIT' | 'PUBLISH' | 'UNPUBLISH' | 'ARCHIVE' | 'UNARCHIVE' | 'DELETE' | 'DESTROY' | 'LOCK' | 'UNLOCK' required

Default system action.

Query parameters

inodestring

Inode of the target content.

identifierstring

Identifier of target content.

indexPolicy'DEFER' | 'WAIT_FOR' | 'FORCE'

Determines how target content is indexed.

ValueDescription
DEFERContent will be indexed asynchronously, outside of the current process. Valid content will finish the method in process and be returned before the content becomes visible in the index. This is the default index policy; it is resource-friendly and well-suited to batch processing.
WAIT_FORThe API call will not return from the content check process until the content has been indexed. Ensures content is promptly available for searching.
FORCEForces Elasticsearch to index the content immediately.<br>Caution: Using this value may cause system performance issues; it is not recommended for general use, though may be useful for testing purposes.
languagestring

Language version of target content.

variantNamestring

Variant name

Request body

commentsstring

Optional comment recorded on the workflow task history.

assignstring

User or role ID to assign the task to (used by actions that reassign).

publishDatestring

Publish date for the 'Publish' step, in the content type's configured date format.

publishTimestring

Publish time for the 'Publish' step, in the content type's configured time format.

expireDatestring

Expiration date, in the content type's configured date format.

expireTimestring

Expiration time, in the content type's configured time format.

neverExpirestring

Set to 'true' to mark the content as never expiring.

whereToSendstring

Push-publishing target environment(s) for a 'Push Publish' action.

filterKeystring

Push-publishing filter key for a 'Push Publish' action.

querystring

Lucene query selecting the content to act on, as an alternative to 'contentlet'.

pathToMovestring

Target folder path for a 'Move' action.

timezoneIdstring

Timezone ID (e.g. 'America/New_York') used to interpret the publish/expire date-times.

individualPermissionsobject

Per-content individual permissions to apply, keyed by permission type (READ, WRITE, PUBLISH, etc.) to a list of user/role IDs.

actionNamestring
iwantTostring
contentletobject

The contentlet to create or edit, as a flat map of field-variable names to values. Polymorphic: the allowed fields depend on the content type.

System fields (all content):

  • contentType (string) — the content type's variable name (e.g. 'webPageContent'). Required when creating.
  • languageId (number) — language ID; defaults to the system default language when omitted.
  • contentHost (string) — host (site) identifier the content belongs to, or hostFolder (string) — a folder identifier. Supply one of these.
  • inode / identifier (string) — include the existing identifier (and optionally inode) to edit existing content; omit both to create new.

⚠️ Do not set host — use contentHost (host id) or hostFolder (folder id) instead.

Host defaulting (common wrong-host trap): if you omit contentHost/hostFolder when creating, the content does NOT go to the current/default site — it inherits the content type's own host, which is SYSTEM_HOST unless the type was explicitly created on a site. So content of a SYSTEM_HOST content type silently lands on SYSTEM_HOST, where it is invisible to another site's URL-maps and to host-scoped searches (+conHost:<siteId>). Always pass contentHost:<siteId> (the site identifier UUID, not the hostname) to place content on a specific site.

Constant fields are content-type configuration, not contentlet data. Fields whose clazz is ImmutableConstantField are shared by the content type. Keys for them in this contentlet map are silently ignored even when the workflow response is HTTP 200/live. For example, a WIDGET's widgetCode must be written to the field's values property via the content-type field API, not included in a widget contentlet.

Pages (contentType: 'htmlpageasset') additionally use:

  • title (string) — the page title.
  • url (string) — the page name (the last URL segment) within hostFolder.
  • template (string) — identifier of the template to render the page.
  • cachettl (number) — page cache time-to-live in seconds.
  • sortOrder (number) — sort order within the folder.

Note: there is no dedicated page-create endpoint — pages are created by firing an action with an htmlpageasset contentlet.

Example request

{
  "contentlet": {
    "contentType": "htmlpageasset",
    "languageId": 1,
    "hostFolder": "48190c8c-42c4-46af-8d1a-0cd5db894797",
    "title": "My Page",
    "url": "my-page",
    "template": "8e63a9c0-...",
    "cachettl": 15,
    "sortOrder": 0
  }
}

Response

Fired action successfully

entityobject
i18nMessagesMapobject
permissionsstring[]

Changes

    • info

      added the new optional query request parameter variantName

    • info

      added the new enum value LOCK to the path request parameter systemAction

    • info

      added the new enum value UNLOCK to the path request parameter systemAction

    • info

      added the media type application/json for the response with the status