Content CRUD Examples

This guide demonstrates how to perform Create, Read, Update, and Delete operations on content (contentlets) in dotCMS using the REST API. This is a quick how-to sketch; for a more complete reference on content lifecycle actions via API, see Saving Content via API.

All write operations go through the same Workflow API endpoint — only the action name in the path changes:

PUT /api/v1/workflow/actions/default/fire/{ACTION}

The POST equivalent of the same endpoint operates over multiple contentlets at once:

Initializing...
Initializing...

Get an API Token#


Before making any API calls, get a token and store it in an environment variable:

export TOK=`curl -H "Content-Type:application/json" -s -X POST -d '{
  "user": "[email protected]",
  "password": "admin",
  "expirationDays": 1,
  "label": "for testing"
}' http://localhost:8082/api/v1/authentication/api-token \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["entity"]["token"])'`

Alternatively, generate a token in the admin panel and set it manually:

export TOK=your_token_here

The examples below all use $TOK for authentication and $conId to hold the content identifier returned by the create step.


Create Content#


To create new content, fire the NEW system action. Omit identifier from the contentlet body and dotCMS will create a new contentlet; include it to update an existing one.

Initializing...

Endpoint: PUT /api/v1/workflow/actions/default/fire/NEW

Parameters:

  • language (query, optional): Language ID (defaults to -1)
  • indexPolicy (query, optional): DEFER, WAIT_FOR, or FORCE

Request Body:

{
  "contentlet": {
    "contentType": "BlogPost",
    "title": "My New Blog Post",
    "body": "This is the content of my blog post",
    "tags": "dotCMS,tutorial"
  }
}

Example — create and store the returned identifier:

export conId=$(curl -s -X PUT "http://localhost:8082/api/v1/workflow/actions/default/fire/NEW?language=1" \
  -H "Authorization: Bearer $TOK" \
  -H "Content-Type: application/json" \
  -d '{
    "contentlet": {
      "contentType": "BlogPost",
      "title": "Getting Started with dotCMS",
      "body": "Welcome to our comprehensive guide..."
    }
  }' | python3 -c 'import json,sys; print(json.load(sys.stdin)["entity"]["identifier"])')

Tip: Including "identifier": "..." in the contentlet body with NEW will save changes to an existing contentlet instead of creating a new one. This is equivalent to using the EDIT action.


Read Content#


Read Single Contentlet#

Initializing...

Endpoint: GET /api/v1/content/{inodeOrIdentifier}

Parameters:

  • inodeOrIdentifier (path, required): The contentlet identifier or inode
  • language (query, optional): Language ID for localization
  • variantName (query, optional): Variant name (defaults to "DEFAULT")
  • depth (query, optional): Relationship depth (-1 for none, defaults to -1)

Example:

curl -s -H "Authorization: Bearer $TOK" \
  "http://localhost:8082/api/v1/content/$conId?language=1"

Search Multiple Contentlets#

Initializing...

Endpoint: POST /api/v1/content/search

Request Body:

{
  "contentType": "BlogPost",
  "query": "dotCMS tutorials",
  "offset": 0,
  "perPage": 20,
  "sortBy": "modDate",
  "sortOrder": "desc"
}

Example:

curl -s -X POST "http://localhost:8082/api/v1/content/search" \
  -H "Authorization: Bearer $TOK" \
  -H "Content-Type: application/json" \
  -d '{"contentType": "BlogPost", "query": "dotCMS", "perPage": 10}'

Update Content#


To update existing content, use the EDIT system action. Pass the identifier either as a query parameter or inside the contentlet body.

Initializing...

Endpoint: PUT /api/v1/workflow/actions/default/fire/EDIT

Parameters:

  • identifier (query, optional): Identifier of the contentlet
  • inode (query, optional): Inode of the contentlet
  • language (query, optional): Language ID (defaults to -1)
  • indexPolicy (query, optional): DEFER, WAIT_FOR, or FORCE

Request Body:

{
  "contentlet": {
    "identifier": "d66309a7378bbad381fda3accd7b2e80",
    "title": "Updated Blog Post Title",
    "body": "Updated content here..."
  },
  "comments": "Updated title and body content"
}

Example:

curl -s -X PUT "http://localhost:8082/api/v1/workflow/actions/default/fire/EDIT?identifier=$conId&language=1" \
  -H "Authorization: Bearer $TOK" \
  -H "Content-Type: application/json" \
  -d '{
    "contentlet": {
      "title": "Updated: Getting Started with dotCMS",
      "body": "This guide has been updated with new information..."
    },
    "comments": "Updated content with latest information"
  }'

Delete Content#


To delete content, fire the DELETE system action.

Initializing...

Endpoint: PUT /api/v1/workflow/actions/default/fire/DELETE

Parameters:

  • identifier (query, optional): Identifier of the contentlet
  • inode (query, optional): Inode of the contentlet
  • language (query, optional): Language ID (defaults to -1)

Request Body:

{
  "contentlet": {
    "identifier": "d66309a7378bbad381fda3accd7b2e80"
  },
  "comments": "Removing outdated content"
}

Example:

curl -s -X PUT "http://localhost:8082/api/v1/workflow/actions/default/fire/DELETE?identifier=$conId&language=1" \
  -H "Authorization: Bearer $TOK" \
  -H "Content-Type: application/json" \
  -d '{"comments": "Content no longer needed"}'

Other Workflow System Actions#


The same endpoint handles the full content lifecycle:

ActionEffect
PUBLISHMake content live
UNPUBLISHRemove content from live environment
ARCHIVESoft-delete (reversible)
UNARCHIVERestore from archive
DESTROYPermanently delete

Example — Publish:

curl -s -X PUT "http://localhost:8082/api/v1/workflow/actions/default/fire/PUBLISH?identifier=$conId&language=1" \
  -H "Authorization: Bearer $TOK" \
  -H "Content-Type: application/json" \
  -d '{"comments": "Publishing approved content"}'

Full Lifecycle Walkthrough#


This sequence creates a content object, publishes it, retrieves it, unpublishes it, archives it, and deletes it — all chained via $conId.

# 1. Get a token
export TOK=`curl -H "Content-Type:application/json" -s -X POST -d '{
  "user": "[email protected]", "password": "admin", "expirationDays": 1, "label": "for testing"
}' http://localhost:8082/api/v1/authentication/api-token \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["entity"]["token"])'`

# 2. Create content, capture the identifier
export conId=$(curl -s -X PUT "http://localhost:8082/api/v1/workflow/actions/default/fire/NEW?language=1" \
  -H "Authorization: Bearer $TOK" -H "Content-Type: application/json" \
  -d '{"actionName":"save","comments":"creating","contentlet":{"contentType":"myBlog","title":"My Post","languageId":"1"}}' \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)["entity"]["identifier"])')

# 3. Read it back
curl -s -H "Authorization: Bearer $TOK" "http://localhost:8082/api/v1/content/$conId"

# 4. Update it
curl -s -X PUT "http://localhost:8082/api/v1/workflow/actions/default/fire/EDIT?identifier=$conId" \
  -H "Authorization: Bearer $TOK" -H "Content-Type: application/json" \
  -d '{"contentlet":{"title":"My Updated Post"},"comments":"edited"}'

# 5. Publish it
curl -s -X PUT "http://localhost:8082/api/v1/workflow/actions/default/fire/PUBLISH?identifier=$conId" \
  -H "Authorization: Bearer $TOK" -H "Content-Type: application/json" \
  -d '{"comments":"publishing"}'

# 6. Unpublish it
curl -s -X PUT "http://localhost:8082/api/v1/workflow/actions/default/fire/UNPUBLISH?identifier=$conId" \
  -H "Authorization: Bearer $TOK" -H "Content-Type: application/json" \
  -d '{"comments":"unpublishing"}'

# 7. Archive it
curl -s -X PUT "http://localhost:8082/api/v1/workflow/actions/default/fire/ARCHIVE?identifier=$conId" \
  -H "Authorization: Bearer $TOK" -H "Content-Type: application/json" \
  -d '{"comments":"archiving"}'

# 8. Delete it
curl -s -X PUT "http://localhost:8082/api/v1/workflow/actions/default/fire/DELETE?identifier=$conId" \
  -H "Authorization: Bearer $TOK" -H "Content-Type: application/json" \
  -d '{"comments":"done"}'

Notes#


  • Content API handles reads; Workflow API handles writes
  • NEW without identifier = create; NEW with identifier = update (same as EDIT)
  • Always specify language when working with multilingual content
  • Use indexPolicy: DEFER in production to avoid blocking on index writes
  • Always include comments in workflow operations — they appear in the workflow history
  • Actions are permission-based; users can only fire actions their role allows