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:
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": "admin@dotcms.com",
"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_hereThe 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.
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 thecontentletbody withNEWwill save changes to an existing contentlet instead of creating a new one. This is equivalent to using theEDITaction.
Read Content#
Read Single Contentlet#
Endpoint: GET /api/v1/content/{inodeOrIdentifier}
Parameters:
inodeOrIdentifier(path, required): The contentlet identifier or inodelanguage(query, optional): Language ID for localizationvariantName(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#
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.
Endpoint: PUT /api/v1/workflow/actions/default/fire/EDIT
Parameters:
identifier(query, optional): Identifier of the contentletinode(query, optional): Inode of the contentletlanguage(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.
Endpoint: PUT /api/v1/workflow/actions/default/fire/DELETE
Parameters:
identifier(query, optional): Identifier of the contentletinode(query, optional): Inode of the contentletlanguage(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:
| Action | Effect |
|---|---|
PUBLISH | Make content live |
UNPUBLISH | Remove content from live environment |
ARCHIVE | Soft-delete (reversible) |
UNARCHIVE | Restore from archive |
DESTROY | Permanently 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": "admin@dotcms.com", "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
NEWwithoutidentifier= create;NEWwithidentifier= update (same asEDIT)- Always specify
languagewhen working with multilingual content - Use
indexPolicy: DEFERin production to avoid blocking on index writes - Always include
commentsin workflow operations — they appear in the workflow history - Actions are permission-based; users can only fire actions their role allows