Scripted Custom Endpoints are used to create custom REST endpoints by calling scripted files that contain only lightweight Velocity code. These endpoints do not require any Java programming, but are powerful enough to perform the complete set of CRUD (Create, Read, Update, and Delete) operations on content, or to integrate with remote systems via REST calls.
Scripted Endpoints have full access to the Request, Response & Session and handle authentication and authorization in the same way the existing REST endpoints do. The files used to implement these endpoints are created and stored as standard dotCMS Files, allowing them to be created, versioned, and fully maintained outside of dotCMS, and can accept parameters and JSON objects passed to them. These endpoints are not a replacement for standard dotCMS REST APIs, but offer an additional flexible, powerful, and lightweight way to create and customize your own “low code” REST endpoints.
You may also use the Scripted Custom Endpoint API to render Velocity code dynamically (rather than storing the Velocity code in a file within dotCMS), allowing you to create scripts that exist entirely external to dotCMS, but still allowing the results of the script code to be determined at run-time.
In the curl examples below, admin passwords are assumed to be admin
; by default, admin passwords default to a random string generated on first startup and displayed in the server logs. This starter password can be pre-configured through the DOT_INITIAL_ADMIN_PASSWORD
environment variable.
Creating a new Scripted Endpoint
To create a new Scripted Endpoint it's needed to create a folder which will represent the resource under application/apivtl
on the desired site/host.
For example, creating the folder application/apivtl/employees
will make available a matching resource (same name) accessible under https://yoursite.com/api/vtl/employees
Once the resource folder is created, vtl files corresponding to each supported HTTP method need to be created under it.
Supported HTTP Methods:
- GET (get.vtl)
- POST (post.vtl)
- PUT (put.vtl)
- PATCH (patch.vtl)
- DELETE (delete.vtl)
When you send a request with one of these HTTP methods, the corresponding VTL file will be rendered, and the result will be sent back to the requesting client. The results are rendered exactly as determined by the VTL code in the file; nothing at all is added to the response body. This means that the logic inside the files, and exactly what to render, is entirely controlled by your implementation.
Interacting with the dotCMS Java API
Since the endpoint uses Velocity to render the response body, you can access the dotCMS API using Velocity Viewtools.
Read Operations
To read and return content from the content repository using the Content Tool.
Update Operations
Update (save/publish/delete) operations are recommended to be performed by using Workflow Actions, which can be fired from velocity by using the $workflowtool
. The $workflowtool.fire(map, action)
method, takes a map of the properties of the contentlet and an actionId as parameters, and returns the resulting ContentMap
object.
Use of the $workflowtool
can be seen in many of the examples endpoint calls below.
How to Access Data from the Request
Scripted Custom endpoints can accept the following (all of which are optional, including the request body):
Path parameter
Available in velocity as single value under $pathParam
E.g. Given the request GET /employees/777f1c6bc8
, the value of the path param can be accessed like this:
$dotcontent.find($pathParam)
Query parameters
Available in velocity as a map under $queryParams
E.g. Given the request /employees?name=dean&company=dotcms
,
the value of the query params can be accessed like this:
$queryParams.get("name")
$queryParams.get("company")
Request Body (JSON)
Available in velocity as a map under $bodyMap
E.g. Given the request GET /employees -d '{"contentType":"Employee","languageId":1}
,
the value of the request body can be accessed like this:
$bodyMap.get("contentType")
$bodyMap.get("languageId")
Responses
Format of the Response
JSON Response Format
The format of the response returned by the endpoint can be handled automatically using the JSON tool Velocity viewtool:
$dotJSON.put(key, value)
Other Response Formats
Other response formats can also be supplied, since Velocity can generate any strings and format you wish. You must manually generate formatting for any other response formats you wish. Please see the Build XML RSS from Content document for an example of generating XML formatting using Velocity code.
Caching the Response
To make returns from custom endpoints performant, there are two caching options:
- If using the dotJSON tool, use
$dotJSON.put("dotcache", numberOfSeconds)
This dotcache parameter takes a number of variables into account when caching a response, including the user's selected language, the persona accessing the respons, the request uri and any request query parameters, e.g.?id=135
. If those match a cached entry, then the cached response will be served. - If manually handling the format of the response , the dotCMS Block Cache tool can be used wrapping the desired code to cache
Error Handling & Response Codes
The following response codes are returned by scripted endpoints:
Type of Error | Code | Standard Code Meaning |
---|---|---|
Success | 200 | OK |
Missing/Invalid data | 400 | Bad Request |
Authentication error | 401 | Unauthorized |
Authorization error | 403 | Forbidden |
Server-side error | 500 | Internal Server Error |
Note: Errors parsing the Velocity code will generate an response code of 500 (Internal Server Error).
Permissions
Permissions control access to both the folder and the .VTL files which implement the scripted endpoint. This means that, for any user to be able to execute the scripted endpoint, they must both:
- Have at least View access to the necessary files and folders, and
- Be authenticated as a dotCMS User when accessing the endpoint.
Examples
The following documentation illustrates how to create GET
, PUT
, POST
, PATCH
, and DELETE
HTTP requests using only Velocity (.vtl) files. Each of these files are responsible for request handling using the HTTP method corresponding to their name. In this way, the logic of each of these HTTP actions can be separated out into lightweight and versionable files.
Sample endpoint URL: http://localhost:8082/api/vtl/employees
GET (Read)
Sample GET Curl Command (To list an employee given an identifier, or all employees if no identifier)
curl -v -u admin@dotcms.com:admin -XGET 'http://localhost:8082/api/vtl/employees'
get.vtl file code (Returns JSON):
#if($pathParam)
#set($employeeList = [$dotcontent.find($pathParam)])
#else
#set($employeeList = $dotcontent.pull("+structureName:Employee +(conhost:$!{host.identifier} conhost:SYSTEM_HOST)",0,"Employee.lastName"))
#end
#set($resultList = [])
#foreach($employee in $employeeList)
#set($person = {})
$person.put("firstName", $employee.firstName)
$person.put("lastName", $employee.lastName)
$person.put("jobTitle", $employee.jobTitle)
$person.put("profilePicPath", $!{employee.photo.shortyUrl})
$person.put("seniorManagement", $employee.seniorManagement.selectValue)
$person.put("phone", $employee.phone)
$person.put("mobile", $employee.mobile)
$person.put("fax", $employee.fax)
$person.put("email", $employee.email)
$person.put("identifier", $employee.identifier)
#foreach($department in $dotcontent.pullRelated("Department-Employee","$!{employee.identifier}",true,1,"modDate"))
#set($departmentName = $department.departmentName)
$person.put("department", $departmentName)
#end
$resultList.add($person);
#end
$dotJSON.put("employees", $resultList)
POST (Create)
Http POST
(post.vtl) is the recommended method to use for creating content.
Sample POST Curl Command (To save a new employee given a request body)
curl -v -u admin@dotcms.com:admin -XPOST http://localhost:8082/api/vtl/employees -H "Content-Type:application/json" -d '{
"contentType":"Employee",
"languageId":1,
"host1":"demo.dotcms.com",
"firstName":"First Name",
"lastName": "Last Name",
"jobTitle": "Architect",
"email": "email@startup.cool",
"mobile": "8325553355"
}'
post.vtl file code
#set($actionId = "b9d89c803d") // save-workflow-action id
#set($savedContent = $workflowtool.fire($bodyMap, $actionId));
$dotJSON.put("savedObject", $savedContent.getMap())
PUT (Full update)
Http PUT
(put.vtl) is the recommended method to use for full replacing existing content.
Sample PUT Curl Command (To completely replace an existing employee given request body, including the identifier of the existing employee)
curl -v -u admin@dotcms.com:admin -XPOST http://localhost:8082/api/vtl/employees -H "Content-Type:application/json" -d '{
"contentType":"Employee",
"identifier":"af047c97-1a91-4642-9cbb-2fbdef4da1cb",
"languageId":1,
"host1":"demo.dotcms.com",
"firstName":"First Name",
"lastName": "Last Name",
"jobTitle": "Architect",
"email": "email@startup.cool",
"mobile": "8325553355"
}'
put.vtl file code
#set($actionId = "b9d89c803d") // save-workflow-action id
// the `identifier` of the contentlet to update can be passed in as a path param
// or in the request body
#set($savedContent = $workflowtool.fire($bodyMap, $actionId));
$dotJSON.put("savedObject", $savedContent.getMap())
PATCH (Partial update)
The patch method is the recommended way to perform a partial submit instead of requiring the entire object be sent, updating only the specific field data sent by the method. In the following implementation an identifier must be supplied when calling the patch method as show in the curl command below.
Sample curl command (only updates/sends the specific fields submitted)
curl -v -u admin@dotcms.com:admin -XPATCH http://localhost:8082/api/vtl/employees/37f93fcb-6124-46af-83b4-9ece6c1c5380 -H "Content-Type:application/json" -d '{"email": "updated@dotcms.com","jobTitle": "updated Job title"}
sample patch.vtl code:
##publish action id on the employee workflow
#set($actionId = "b9d89c803d")
##Reads what is passed in the URL
#set($contentToPatch = $dotcontent.find($pathParam))
#set($patchedMap = $contentToPatch.contentObject.getMap())
##Builds the JSON object using the form submitted fields
#foreach($key in $bodyMap.keySet())
#if($patchedMap.get($key))
$patchedMap.put($key, $bodyMap.get($key))
#end
#end
##Performs the patch submit of the data send in the form using the specified workflow action
#set($savedContent = $workflowtool.fire($patchedMap, $actionId));
$dotJSON.put("savedObject", $savedContent.getMap())
DELETE
In the presented implementation the delete.vtl code merely takes the identifier of the content to find it and call the $workflowtool
with the proper (delete) actionId. It is the responsibility of the workflow action to have all the require sub-action to unpublish/archive/delete the content that is passed by the delete.vtl code.
Sample DELETE Curl Command
curl -v -u admin@dotcms.com:admin -XDELETE http://localhost:8082/api/vtl/employees/37f93fcb-6124-46af-83b4-9ece6c1c5380
Example delete.vtl code:
#set($actionId = "777f1c6bc8")
#set($contentToDelete = $dotcontent.find($pathParam))
$workflowtool.fire($contentToDelete.contentObject.map, $actionId);
How to return the employee List as JSON
From the Read example above, it's just needed to put the resulting list in the $dotJSON
object for it to be rendered as JSON as follows:
$dotJSON.put("employeeList", $resultList)
How to return the employee List as XML by manually formatting
<employees>
#foreach($employee in $employeeList)
<employee>
<firstName>$employee.firstName</firstName>
<lastName>$employee.lastName</lastName>
<jobTitle>$employee.jobTitle</jobTitle>
<profilePicPath>$!{employee.photo.shortyUrl}</profilePicPath>
##<seniorManagement>$!{employee.seniorManagement.selectedValues[0]}</seniorManagement>
<phone>$employee.phone</phone>
<mobile>$employee.mobile</mobile>
<fax>$employee.fax</fax>
<email>$employee.email</email>
<firstName>$employee.firstName</firstName>
#foreach($department in $dotcontent.pullRelated("Department-Employee","$!{employee.identifier}",true,1,"modDate"))
#set($departmentName = $department.departmentName)
<departmentName>$departmentName</departmentName>
#end
</employee>
#end
</employees>
Dynamic Endpoint
The Dynamic Endpoint makes it possible to execute Velocity code by sending it in the request Body and returning the results of the Velocity code.
The Dynamic Endpoint is accessible via the path api/vtl/dynamic
.
There are two options for sending the Velocity code to execute:
- Embed the Velocity in a property of the JSON Object sent in the request body.
- Send the Velocity as the request body without embedding it in a JSON Object.
Example: Simple Dynamic Get
This example includes the Velocity code n the request body.
curl -u admin@dotcms.com:admin -XGET http://localhost:8082/api/vtl/dynamic/ \
-H "Content-Type:text/plain" \
-d '
$dotJSON.put("myDate", $date.toString())
Example: Dynamic POST to a Specific Workflow Step
This example embeds the Velocity code in the JSON object passed via the request.
curl -v -u admin@dotcms.com:admin -XPOST http://localhost:8082/api/vtl/dynamic -H "Content-Type:application/json" -d '{
"contentType":"Movie",
"languageId":1,
"host1":"demo.dotcms.com",
"title":"dotCMS 5.1 EXTRA",
"genre":"thriller,mystery",
"posterImage":"//localhost:8082/images/company_logo.png",
"year": 2019,
"rottenTomatoes": 9.9,
"imdb": 10.0,
"releaseDate": "01/01/2019",
"length": 30,
"studio":"+studio.name:dotCMS*",
"velocity": "
#set($actionId = \"b9d89c803d\")
#set($savedContent = $workflowtool.fire($bodyMap, $actionId))
$dotJSON.put(\"savedObject\", $savedContent.contentObject.getMap())"
}'
Sending Files
When sending content with one or more file fields thru a custom endpoint, first use the binaryFields
parameter in the JSON to declare the velocity variable name(s) of the field(s) you will be sending to create/update the content. The order of the field order supplied to the binaryFields
parameter can be in any order, but needs to match the order in which the files are sent. Passing files in this way will work for all binary fields, file fields, and image fields on Content Types.
In the example below, the first file being sent will be added to the photo field and the second file will be added to the “photo2” field:
"binaryFields": [ "photo", "photo2" ]};
type=application/json" -F "file=@/Users/danielsilva/Downloads/firstFile.jpg; type=application/jpg" -F "file=@/Users/danielsilva/Downloads/secondFile.jpg; type=application/jpg"
DELETING EXISTING FILES ON CONTENT/PASSING NULL FILE VALUES (delete):
To delete any existing file from a contenlet, pass the velocity variable name to the binaryFields
tool but do not send a file. For multipart files, leave the names of the fields to be deleted at the end of the binaryFields
list to delete them. In the example below, the “photo2” field will be updated with a NULL value since only the first file was sent to the declared “photo” field.
"binaryFields": [ "photo", "photo2" ]};
type=application/json" -F "file=@/Users/danielsilva/Downloads/firstFile.jpg; type=application/jpg"
MULTIPART POST (create):
curl -v -u admin@dotcms.com:admin -POST http://localhost:8082/api/vtl/employees -F "json={
"contentType":"Employee",
"languageId":1,
"host1":"demo.dotcms.com",
"firstName":"Daniel3",
"lastName": "Silva3",
"jobTitle": "Dev",
"email": "daniel.silva@dotcms.com",
"mobile": "8325553355",
"gender": "male",
"binaryFields": [ "photo", "photo2" ]};
type=application/json" -F "file=@/Downloads/firstFile.jpg; type=application/jpg" -F "file=@/Users/danielsilva/Downloads/secondFile.jpg; type=application/jpg"
MULTIPART PUT (full content update):
curl -v -u admin@dotcms.com:admin -XPUT http://localhost:8082/api/vtl/employees -F "json={
"identifier":"af047c97-1a91-4642-9cbb-2fbdef4da1cb",
"contentType":"Employee",
"languageId":1,
"host1":"demo.dotcms.com",
"firstName":"Daniel2",
"lastName": "Silva2",
"jobTitle": "Dev",
"email": "daniel.silva@dotcms.com",
"mobile": "8325553355",
"gender": "male",
"binaryFields": [ "photo", "photo2" ]};
type=application/json" -F "file=@/Downloads/testImages/testImage1.jpg; type=application/jpg" -F "file=@/Users/danielsilva/Downloads/secondFile.jpg; type=application/jpg"
MULTIPART PATCH (partial content update without altering other fields not sent):
curl -v -u admin@dotcms.com:admin -XPATCH http://localhost:8082/api/vtl/employees/af047c97-1a91-4642-9cbb-2fbdef4da1cb -F "json={
"lastName": "SilvaPatched",
"jobTitle": "DevPatched",
"binaryFields": [ "photo2" ]};
type=application/json" -F "file=@/Downloads/secondFile.jpg; type=application/jpg"
Files can also be sent when using the Dynamic Endpoint in the same way as Scripted Endpoints.
Referencing Site Variables
When referencing Site Variables in the Scripting API the $host_variable
must be added to your context.
To add $host_variable
to the context add the following to the top of the VTL file. Make sure to replace [SITE_ID]
with either the site identifier or the site key:
#parse("LIVE/[SITE_ID].site")
For more information on Site Variables take a look at the Site Variables page.