Saving Content - Workflows

Last Updated: Mar 18, 2024
documentation for the dotCMS Content Management System

The Content Workflow API allows you to perform an available lifecycle or workflow action on a content object, things like saving, updating, translating, archiving and deleting. The API allows you to specify a specific workflow action you would like to perform or you can call a standard content lifecycle action.

There are 3 main Content Workflow API endpoints that should be used for all content operations.

Content WorkflowDescription 
Content Lifecycle ActionsExecutes the specified content lifecycle action. You can map each of these content lifecycle events to specific workflow actions. Default Workflow Action for the content (as specified in the Content Type of the content). These default lifecycle actions can be one of the following: NEW, EDIT, PUBLISH, UNPUBLISH, ARCHIVE, UNARCHIVE, DELETE or DESTROY. 
Fire Workflow Action by NameExecutes the Workflow Action specified by name in the request body. 
Fire Workflow Action by IdExecutes the Workflow Action specified in the URI. 

Content Lifecycle Actions

The default fire /api/v1/workflow/actions/default/fire/_{defaultAction}_ api gives developers consistent endpoints to perform standard content lifecycle operations. Importantly (and optionally) each of these DEFAULT actions can map content lifecycle action to a specific workflow action, which can be modified to perform additional processing. dotCMS administrators can set a workflow's Default Actions that execute when a content object is PUT against these endpoints. If there is no default lifecycle action specified for content type or the content types workflow, the content will perform the standard content lifecycle event - SAVE/PUBLISH/UNPUBLISH/DELETE and return normally

Available Lifecycle Actions

Lifecycle ActionUse when
NEWCreating new content
EDITEditing content
PUBLISHPublishing content
UNPUBLISHUnpublishing content
ARCHIVEArchiving content
UNARCHIVEUnarchiving content
DELETEDeleting all versions of a content in a single specified language
DESTROYDeleting all versions of a content in ALL languages

All lifecycle actions are called by lifecycle action name, e.g.

PUT /api/v1/workflow/actions/default/fire/SAVE {...}
PUT /api/v1/workflow/actions/default/fire/EDIT {...}
PUT /api/v1/workflow/actions/default/fire/PUBLISH {...}
PUT /api/v1/workflow/actions/default/fire/UNPUBLISH {...}
PUT /api/v1/workflow/actions/default/fire/ARCHIVE {...}
PUT /api/v1/workflow/actions/default/fire/UNARCHIVE {...}
PUT /api/v1/workflow/actions/default/fire/DELETE {...}
PUT /api/v1/workflow/actions/default/fire/DESTROY {...}

All lifecycle actions can take a {contentlet:xxxx} body that will be persisted when the default action is called (the body is required when calling NEW and EDIT).

Mapping Lifecycle to Workflow Actions

To map content lifecycle actions to specific workflow actions, navigate to the workflow you would like to edit/map and click the “Default Actions” button.

Example: New Content

PUT /actions/default/fire/NEW
{
    "contentlet": {
      "contentType":"webPageContent",
      "title":"Amazing Content", 
      "siteOrFolder":"demo.dotcms.com",
      "body": "<h1>This content is amazing</h1><div>but not this</div>",
      "languageId": "1"
  }
}   

Example: Publishing

PUT /actions/default/fire/PUBLISH
{
    "contentlet": {
      "contentType":"webPageContent",
      "title":"Published Content", 
      "siteOrFolder":"demo.dotcms.com",
      "body": "<h1>This content is amazing</h1><div>but not this</div>",
      "languageId": "1"
  }
}   

Example: Unpubishing

PUT /actions/default/fire/UNPUBLISH?identifier=e5b3e417-b0c9-450c-87ba-dda0e2782cb3

Example: Archiving

PUT /api/v1/workflow/actions/default/fire/ARCHIVE 
{
    "contentlet": {
      "identifier":"e5b3e417-b0c9-450c-87ba-dda0e2782cb3",
      "contentType":"webPageContent",
      "title":"ARCHIVED Content", 
      "contentHost":"default",
      "body": "I'M ARCHIVED!",
      "languageId": "1"
  }
}   

Example: Multiple Contents

Many of these endpoints take multiple content obects as an array, and will perform the same Lifecycle action on the list of contents.

POST /actions/default/fire/PUBLISH
{

    "contentlets":[
        {
            "contentType":"Test1",
            "title":"Content1",
            "languageId": "1",
            "body":"Body Content1"
        },
        {
            "contentType":"Test1",
            "title":"Content2",
            "languageId": "1",
            "body":"Body Content2"
        },
        {
            "contentType":"Test1",
            "title":"Content3",
            "languageId": "1",
            "body":"Body Content3"
        }
    ]
}

Example: PATCHING Update

The EDIT and PUBLISH content lifecycle events can be used to modify specific fields on multiple pieces of content simultaniously. In order to do this, you use the HTTP PATCH method and specify a content query that will return the list of content to update and then field/values that you would like to update. Below is an psuedo-code example:

PATCH actions/default/fire/PUBLISH
{
    "comments":"Publish an existing Generic content",
    "query":"+contentType:webPageContent AND title:\"Test Merge Content\"",
    "contentlet": {
        "title":"Test Merge Content Published"
    }
}

Fire by Name or ID

PUT /api/v1/workflow/actions/fire {"actionName": "save"...}
PUT /api/v1/workflow/actions/{actionId}/fire

The fire endpoint expects data in the body of the request with the JSON name "contentlet", which includes a list of the fields of the content and a “name” field which specifies the name of the application accessing the endpoint (for logging purposes).

If you wish to specify the Workflow Action via the action name, you must include an actionname field as well. This field is not required when specifying the Workflow Action identifier in the URL.

This method allows you to post content and specify a workflow action that you want to fire it through.

Example: Save by actionName

PUT /api/v1/workflow/actions/fire
{
    "actionName": "save",       // workflow action name or action id
    "comments": "saving content",
    "contentlet": {
      "contentType":"myBlog",
      "title":"Amazing Content", 
      "urlTitle": "amazing-content",
      "siteOrFolder":"demo.dotcms.com",
      "publishDate":"2019-12-18 10:00:00",
      "tags": "tag one, tag two",
      "body": "<h1>This content is amazing</h1><div>but not this</div>",
      "languageId": "1"
  }
}   

Example: Update by actionName

To update content via the api, the identifier and language id are required.

PUT /api/v1/workflow/actions/fire
{
    "actionName": "save",             // workflow action name or action id
    "comments": "saving content",     // include a workflow comment
    "contentlet": {
      "identifier":"fec7b960-a8bf-4f14-a22b-0d94caf217f0" // the identifier of the content you are updating
      "title":"Amazing Content Again", 
      "urlTitle": "my-second-url-title",
      "siteOrFolder":"demo.dotcms.com",
      "publishDate":"2019-12-18 11:00:00",
      "tags": "tag one, tag two",
      "body": "<h1>This is an EDITED content<h2><div>but not this</div>",
      "languageId": "1"
  }
}   

Example: Just Fire A Workflow

Some workflow actions do not change/modify the content and rather do other things with them. For example, sending a content object to a translation step. In this case, you can specify the content by Id as a query parameter and the workflow action in the body of the request.

PUT /api/v1/workflow/actions/fire?identifier=fec7b960-a8bf-4f14-a22b-0d94caf217f0
{
    "actionName": "Publish",
    "comments": "publishing content"
}

Additionally, you can specify which workflow action to fire in the URI - this is useful when you are firing a specific Workflow Action that you know beforehand. You can also specify the workflow action you wish to fire using a Shorty version of the id, e.g. a22b0d94ca vs a22b0d94-caf21-4f14-a22b-0d94caf217f0

Example: Update by Id

PUT /api/v1/workflow/actions/a22b0d94caf217f0/fire                 // workflow action id
{
    "comments": "saving content",     // include a workflow comment
    "contentlet": {
      "identifier":"fec7b960-a8bf-4f14-a22b-0d94caf217f0" // the identifier of the content you are updating
      "title":"Amazing Content Again", 
      "urlTitle": "my-second-url-title",
      "siteOrFolder":"demo.dotcms.com",
      "publishDate":"2019-12-18 11:00:00",
      "tags": "tag one, tag two",
      "body": "<h1>This is an EDITED content<h2><div>but not this</div>",
      "languageId": "1"
  }
}   

Files / Multipart Form Data

Sending multipart/form-data is supported, and is generally required in order to send file content for Binary fields, unless creating Binary content via string.

Content Type Header

The format of the content should be specified in the Content-Type header. Supported Content-Types include:

  • application/json
  • application/x-www-form-urlencoded

For more information on submitting content using these content types, please see Data Formats, below.

Auth & Anonymous Access

This API supports the same REST authentication methods as other REST APIs in dotCMS. Please read the REST API Authentication documentation for details on the different methods and how to use them when saving content using the REST API.

By default dotCMS respects the permissions set on the content type that is being persisted. This means that all users, anonymous or otherwise, may save content via the REST API if they have permissions on the content type and or site they are saving content to.

Anonymous access can be limited by assigning the system configuration property, such as by environment variables:

CONTENT_APIS_ALLOW_ANONYMOUS=WRITE

Possible values are NONE | READ | WRITE.

The CONTENT_APIS_ALLOW_ANONYMOUS controls what level of access to grant ANONYMOUS (not logged in) visitors to dotCMS Content apis. This property is only respected by the APIs that have to do with mananging content in the dotCMS content store. For anonymous content submittal to work (form builder, contentAPI) CONTENT_APIS_ALLOW_ANONYMOUS needs to be set to WRITE, otherwise users will need to authenticate before subitting content

  1. Set permissions to allow the CMS Anonymous user to save content of the appropriate Content Types.
    • Grant Add to permissions to the CMS Anonymous role on the Site where the content will be saved using the REST API.
    • Grant Edit permissions to the CMS Anonymous role on each Content Type which will be added or updated via the REST API.

For more configuration on how access to the REST API can be controlled via configuration, please see the REST API Authentication documentation.

Data Formats

You may submit data via the Content REST API using either JSON or FORM. The following examples demonstrate ways to submit content via the REST API using JSON, and Form UrlEncoded formats, both to create new content and to update existing content.

Field Types

The following examples demonstrate how to send content for several additional types of Content Type fields.

Field Value Formats

All field values are passed in the json object as strings. Below is a list of field types in dotCMS and the supported formats for each type:

Field TypeSupported Formats
BinaryPlease see Binary Fields, below.
CategoryA string containing a comma separated list of Category ids, Category keys, or Category variable names.
(e.g. "investing,research,wealthManagement").
Please see Categories, below.
Checkbox,
Multi-Select
A string containing a comma separated list of selected values (the strings to the right of the pipe (\
Constant Field,
Hidden Field
Should not be submitted with the content item.
(These are filled in automatically by dotCMS).
Custom FieldA string containing the field value (as formatted by your Custom Field code).
Date,
Time,
Date and Time
yyyy-MM-dd HH:mm:ss, yyyy-MM-dd HH:mm, d-MMM-yy, MMM-yy, MMMM-yy, d-MMM, dd-MMM-yyyy, MM/dd/yyyy hh:mm:ss aa, MM/dd/yyyy hh:mm aa, MM/dd/yy HH:mm:ss, MM/dd/yy HH:mm:ss, MM/dd/yy HH:mm, MM/dd/yy hh:mm:ss aa, MM/dd/yy hh:mm:ss, MM/dd/yyyy HH:mm:ss, MM/dd/yyyy HH:mm, MMMM dd, yyyy, M/d/y, M/d, EEEE, MMMM dd, yyyy, MM/dd/yyyy, hh:mm:ss aa, hh:mm aa, HH:mm:ss, HH:mm, yyyy-MM-dd.
Note: You may use formats that only include the date or time for Date and Time fields, but you may not use a format which includes both the date and time for Date fields or Time fields.
File,
Image
The path to the related file, starting with the hostname (e.g. "//demo.dotcms.com/images/photos/The-Gherkin-London-England.jpg").
Key/ValuePlease see Key/Value Pair Fields, below.
Radio,
Select
A string containing the selected value for the field (the string to the right of the pipe (\
RelationshipPlease see Relationships, below.
Site or FolderA string representing the host (e.g. "demo.dotcms.com"), a folder (for the host the user is on) (e.g. "/images/photos"), or a combination of host and folder (e.g. "demo.dotcms.com:/images/photos/".
TagA comma separated list of tag values (e.g. "investment,banking,europe").
TextA string containing the field value.
TextareaA string containing the field value.
Non-printing characters should be escaped using HTML escape codes (e.g. %0D%0A for carriage-return line-feed).
WYSIWYGA string containing the field value, containing HTML formatting.
Non-printing characters should be escaped using HTML escape codes (e.g. %0D%0A for carriage-return line-feed).

Categories

Categories can be specified using a comma separated list. Each item in the list will be checked first to see if it matches a Category inode, then to see if it matches a Category key, and finally to see if it matches a Category variable name. If any of these are matched, the appropriate Category will be set for the content item.

The following example adds a News content item on the dotCMS demo site (uploading categories variable names):

Example: Categories

PUT /api/v1/workflow/actions/default/fire/PUBLISH 
{
   "contentlet": {
      contentType:"webPageContent",
      languageId:1,
      urlTitle:"a-new-news-item",
      hostfolder:"demo.dotcms.com",
      title:"A new news item",
      byline: "this is a new story",
      sysPublishDate: "2013-07-01 00:00:00",
      story: "this is a new story uploaded from cURL",
      topic: "investing,banking,research"
   }
}

Binary Fields

Files can be uploaded into Binary fields using multipart/form-data.

  • Portions of the API call using JSON, XML or Form UrlEncoded data are interpreted as normal field values.
  • Portions of the API call using a Content-Disposition header are added as Binary field values.

Single or multiple binary files can be uploaded as part of a piece of content. Regardless of which method is used, all required binary fields on the Content Type must be submitted.

Uploading a Single Binary File

The following curl command submits a single binary image to a File Asset type of content using the REST API. Note the JSON portion of the API call to set the regular field values, and the file included using @ (which will submit the specified file to the first Binary field in the Content Type).

Example: New Binary Asset

curl --location --request PUT 'http://localhost:8082/api/v1/workflow/actions/default/fire/NEW?language=1589383514071' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic XXXXXXXXXXXXXXXXX==' \
--form 'file=@testpdf.pdf' \
--form 'json={
     "contentlet": {
        "contentType":"PDFDotAsset",
        "title":"Test1",
        "contentHost":"demo.dotcms.com"
    }
}
'

Uploading Multiple Binary Fields

The following curl command submits a File Asset using the REST API with two binary fields. The names of the two fields are fileAsset and image2. Note the JSON portion of the API call to set the regular field values, and the file included using @ (which will submit the specified file to the Binary fields in the order expressed using in the binaryFields parameter that has been added to the curl command).

Important Note: Notice that the binaryFields parameter is ordering the submit of the files to the fields in the content. When using the binaryFields parameter:

  1. The order of the fields submitted to the parameter determines which fields the binary files will be uploaded.
  2. Non-required binary fields may be omitted — e.g., if the Content Type has 4 non-required binary fields, supplying the names of only the 2nd and 3rd fields to the binaryFields parameter will upload only two binary files to those specified fields and leave the other two Binary fields empty.
  3. The number of fields supplied to the binaryFields parameter must match the number of files that are appended at the end of the REST API submit.
  4. The names of each binary field will match the file name specified in the REST API submit. More flexibility on multiple binary field naming will added in a future dotCMS version.

The curl command below uses the binaryFields parameter two upload two files to a new contentlet of the File Asset type. The order of the fields supplied to the parameter, in the example provided below, determines which fields the appended files will be uploaded to respectively i.e., test1.png will be uploaded to the fileAsset field, and test2.png will be uploaded to the image2 field.

Example: Multiple Binary Assets

curl -XPUT 'http://local.dotcms.site:8443/api/v1/workflow/actions/default/fire/PUBLISH' \
--header 'Authorization: Bearer XXXXXXXXXXXXXX==' \
--header 'Content-Type: application/json' \
--form 'json="{
    \"contentlet\": {
        \"stInode\": \"49f7f5904c2c10d3a4ada6b2e9cc2fc1\",
        \"title\":\"Test\",
        \"hostFolder\":\"demo.dotcms.com\"
    },
    \"binaryFields\": [
            \"fileAsset\",\"image2\"
        ]
}"' \
--form 'file=@"/Users/erickg/Pictures/test1.png"' \
--form 'file=@"/Users/erickg/Pictures/test2.png"'

Creating Binary Content Via String

Beginning with dotCMS 23.06, it is possible to populate a Binary field using only a string, thereby bypassing the multi-part process. Please note that this technique applies only to plaintext-type target files — e.g., JSON, XML, TXT, VTL, HTML, etc. Attempting to encode raw binary data as, e.g., a base-64 string and transmit it in this same fashion may fail.

For example, the following will create a plaintext File Asset:

Example: New Text Binary Asset

curl -XPUT 'http://local.dotcms.site:8443/api/v1/workflow/actions/default/fire/PUBLISH' \
--header 'Authorization: Bearer XXXXXXXXXXXXXX==' \
--header 'Content-Type: application/json' \
--data-raw '{
    "contentlet": {
        "hostFolder":"default",
        "title": "greatings.txt",
        "fileName": "greatings.txt",
        "fileAsset": {
            "fileName": "greatings.txt",
           "content": "Hello dotCMS!"
         },
        "contentType": "FileAsset"
    }
}'

Key/Value Fields

In the following example, a Key/Value pair field on a Content Type called “Television” has the Velocity variable name of “productSpecifications”. As shown in the example below, keys and values can be sent via REST API using the following form: fieldVelocityVariableName:{"mykey1:"myValue1","mykey2:"myValue2",... etc.}. Note the handling of the “productSpecifications” key/value pair field.

Example: Key/Value

curl -XPUT 'http://local.dotcms.site:8443/api/v1/workflow/actions/default/fire/PUBLISH' \
--header 'Authorization: Bearer XXXXXXXXXXXXXX==' \
--header 'Content-Type: application/json' \
-d '{
"contentlet": 
   {
   contentType:"Television",
   languageId:1,
   contentHost:"demo.dotcms.com",
   brandAndModel:"Samsung UN65JS9500",
   resolution:"Ultra HD",
   screenSizeinches:"65",
   productSpecifications:{"Refresh Rate":"240 CMR (Effective)","Backlight":"LED","Smart Functionality":"Yes - Built in Wifi","Inputs":"2 HDMI, 2 USB"}
   }
}'

Relationships

The examples below show how to submit related content via REST API using one-sided relationship fields. This methodology works for One-to-One, One-to-Many, or Many-to-Many relationships.

Submiting Related Content

To relate content, you must specify the variable name of the Relationship field, and set the value to a string with one or more lucene queries and/or content identifiers, separated by commas. For content that already has existing relationships, you may delete, replace, or preserve the existing relationships by passing the appropriate value to the Relationship field, as follows:

Information Passed to the Relationship FieldResults
Empty String (e.g. "")Remove all existing relationships (if any).
String of queries/ids (separated by commas)Replace existing relationships (if any) with the specified relationships.
No relationship field passedPreserve (make no changes to) existing relationships (if any).

When submiting related content via the REST API, use the following format:

Relate a single piece of content by identifier

{relationship field velocity variable name}:"{identifier}"  

Relate a multiple pieces of content by identifier

{relationship field velocity variable name}:"{identifier,identifier,...}"

Relate one or more pieces of content using a lucene query

{relationship field velocity variable name}:"{lucene query}"  

Relate one or more pieces of content using a comma separated list of lucene queries and identifiers

{relationship field velocity variable name}:"{lucene query},{identifier},{lucene query},{identifier}"  

Examples: Related Content

Create New Employee with Query for Location Identifier

curl -XPUT 'http://local.dotcms.site:8443/api/v1/workflow/actions/default/fire/PUBLISH' \
--header 'Authorization: Bearer XXXXXXXXXXXXXX==' \
--header 'Content-Type: application/json' -d '{
   "contentlet": {
      contentType:"Employee",
      languageId:1,
      firstName:"Dean",
      lastName:"Gonzalez",
      gender:"male",
      jobTitle:"General Manager",
      email:"dean@dotcms.com",
      location:"+identifier:0c69da5c-2a05-452f-9305-3be6926d5079"
   }
}'

Create New Employee with Lucene Query for “South America” as Location Office Name

curl -XPUT 'http://local.dotcms.site:8443/api/v1/workflow/actions/default/fire/NEW' \
--header 'Authorization: Bearer XXXXXXXXXXXXXX==' \
--header 'Content-Type: application/json' -d '{
   "contentlet": {
      contentType:"Employee",
      languageId:1,
      firstName:"New",
      lastName:"Person",
      gender:"male",
      jobTitle:"General Manager",
      email:"person@dotcms.com",
      location:"+contentType:Location +Location.title:\"South America\""
   }
}'

Submit new Location and Relate to Many Employees with Job Title of President

curl -XPUT 'http://local.dotcms.site:8443/api/v1/workflow/actions/default/fire/NEW' \
--header 'Authorization: Bearer XXXXXXXXXXXXXX==' \
--header 'Content-Type: application/json' -d '{
   "contentlet": {
      contentType:"Location",
      host1:"48190c8c-42c4-46af-8d1a-0cd5db894797",
      languageId:1,
      title:"Melbourne Office",
      country:"Australia",
      address1:"1122 Aussie Avenue",
      city:"Melbourne",
      segment:"banking,investing",
      description:"Melbourne Australia Office",
      employees:"+contentType:Employee +Employee.jobTitle:President*"
   }
}'

Submit new Location and Relate to Many Employees with Job Title of President and an Employee Identifier

curl -XPUT 'http://local.dotcms.site:8443/api/v1/workflow/actions/default/fire/PUBLISH' \
--header 'Authorization: Bearer XXXXXXXXXXXXXX==' \
--header 'Content-Type: application/json' -d '{
   "contentlet": {
      contentType:"Location",
      host1:"48190c8c-42c4-46af-8d1a-0cd5db894797",
      languageId:1,
      title:"Melbourne Office",
      country:"Australia",
      address1:"1122 Aussie Avenue",
      city:"Melbourne",
      segment:"banking,investing",
      description:"Melbourne Australia Office",
      employees:"+contentType:Employee +Employee.jobTitle:President*,37f93fcb-6124-46af-83b4-9ece6c1c5380"
   }
}'

Update an Employees Passing Employee Identifier and Identifier of Office Location to the Location Field

Please note that the employee's relationship to ANY current office location would be replaced by the new office location being passed to the relationship field

curl -XPUT 'http://local.dotcms.site:8443/api/v1/workflow/actions/default/fire/PUBLISH' \
--header 'Authorization: Bearer XXXXXXXXXXXXXX==' \
--header 'Content-Type: application/json' -d '{
   "contentlet": {
      contentType:"Employee",
      identifier:"74ebbf55-2821-4b51-8e66-78d54c839991",
      location:"6f19f0a0-f407-4f4c-8c01-58daf6659321"
   }
}'

On this page

×

We Dig Feedback

Selected excerpt:

×