Lightweight Endpoints: Writing Custom REST Endpoints Without Java

Modern corporate content and marketing initiatives have moved beyond mere web campaigns linked to a website and have become personalized digital experiences built thru web application experiences that are tailored to the visitor. Single page Applications (SPA), or mobile first development is now the norm when implementing a new corporate digital experience. dotCMS has responded to the mobile first methodology by having robust REST API’s, in addition to having a complete set of presentation layer tools. The ability to develop “No JAVA” complicated customized endpoints with dynamic content listings has been possible (using blank templates and .vtl files), but not very convenient and had no real helper tools in the past.

As a part of our “No Code or Low Code” initiative, dotCMS has already made customizing page layouts in the UI presentation layer, a drag-n-drop, no code experience which content publishers can already enjoy in the dotCMS 5.x series. For our “headless” implementation clients, dotCMS will soon be releasing a “Low Code” Lightweight Endpoint feature which allows front-end developers to customize rest endpoints without plugins or JAVA development.

dotCMS has already developed a solution for front end developers to be able to create their own custom REST endpoints using only lightweight Velocity code, but still perform the complete set of CRUD operations on content. dotCMS will soon release Lightweight Endpoints as a core feature, enabling webmasters to implement responses for GET, PUT, POST, PATCH, and DELETE HTTP requests using only Velocity (.vtl) files. JAVA developers will no longer be required to perform queries or operations on any content objects that Velocity has access to. This will significantly decrease the overhead and development time required to create custom REST API endpoints.

Front-end web developers will now have more control over their own custom REST endpoints without requiring server access, plugin creation, or reliance on a JAVA developer. Webmasters will now be able manage data via RESTful web services while leveraging the full Velocity toolset to perform full CRUD operations on content.

The Structure of Lightweight Endpoints

  1. The Lightweight Endpoint  Resource files MUST exist on the following path: “/application/apivtl/{ResourceName}” under the host (Ex:
  2. The GET, PUT, POST, PATCH, and DELETE methods can be called only by a .vtl file with their corresponding method name that has the code inside it to perform the specified operation:
    1. get.vtl
    2. put.vtl
    3. post.vtl
    4. patch.vtl
    5. delete.vtl

What Can You Do In Your VTL Files?

The resource .vtl file(s) being called need to provide all of the queries & Velocity Code required to perform the desired operation and submit/return the data object using the desired format.  The .vtl file can:

Interact with dotCMS Java API via (View) Tools

Most commonly used: Content Tool - $dotcontent tool and methods
New:  Workflow Tool - $, action) (post, put, patch, delete)

Access data from the request

Path parameter ($pathParam). E.g.  GET /employees/777f1c6bc8
Query parameters ($queryParams). E.g. GET /employees?name=dean&company=dotcms


Body JSON data ($bodyMap) - E.g. GET /employees -d '{"contentType":"Employee","languageId":1}


Return in different ways

  1. Assisted JSON: dotJSON object - $dotJSON.put(key, value)
  2. "Freestyle" returns: Manual handling of formatting.

How to Cache the Response

    1) dotJSON object: $dotJSON.put("dotcache", 15000) (time in ms)
    2) "Freestyle" method: Use the dotCMS Block Cache tool* (#dotcache)



dotCMS Permissions are checked on the resource folder and .vtl files to ensure that access to the API call is restricted to only the intended users and/or user roles.

Error Handling & Response Codes

The following response codes are returned for help with success validation & troubleshooting when creating Lightweight Endpoints.

  • Server-side errors (Including parsing the VTL): 500 Internal Server Error
  • Missing/Invalid data: 400 Bad Request  
  • Authorization errors: 403 Forbidden
  • Success: 200 OK


Here are some examples of basic starter code for all CRUD Operations and how to call each REST method from an custom application or a REST API form.

Create (PUT of POST methods)

(Where put.vtl or post.vtl file exists in the following location: “/application/apivtl/employees/put.vtl” OR “/application/apivtl/employees/post.vtl”)

New content can be created by using either the PUT (put.vtl) or POST (post.vtl) methods. These .vtl files should send (from an application or form) all of the required data fields for content of the type being sent.

Sample PUT & POST Curl Commands


curl -v -u -XPOST http://localhost:8080/api/vtl/employees -H "Content-Type:application/json" -d '{
    "firstName":"First Name",
    "lastName": "Last Name",
    "jobTitle": "Architect",
    "email": "",
    "mobile": "8325553355",
    "wfActionId": "b9d89c80-3d88-4311-8365-187323c96436",
    "wfActionComments": "Posting new employee"

PUT ( must include content identifier being updated )

curl -v -u -XPOST http://localhost:8080/api/vtl/employees -H "Content-Type:application/json" -d '{
     "firstName":"First Name",
     "lastName": "Last Name",
     "jobTitle": "Architect",
     "email": "",
     "mobile": "8325553355",
     "wfActionId": "b9d89c80-3d88-4311-8365-187323c96436",
     "wfActionComments": "Posting new employee"

put.vtl OR post.vtl code (both merely send a workflow action id and a content object)

#set($actionId = "b9d89c803d")
#set($savedContent = $$bodyMap, $actionId));
$dotJSON.put("savedObject", $savedContent.getMap())

Read (GET - list JSON or any other format)


Calling the resource will automatically trigger the execution of the get.vtl which should have the velocity code/tools inside the file to return the desired data in the desired format (JSON, XML, etc.)

Sample GET Curl Command (To list all employees)

To List all employees:

curl -v -u -XGET 'http://localhost:8080/api/vtl/employees'

To List an employee given its id:

curl -v -u -XGET 'http://localhost:8080/api/vtl/employees/{identifier}

get.vtl file code (Returns employees as JSON):

   #set($employeeList = [$dotcontent.find($pathParam)])
    #set($employeeList = $dotcontent.pull("+structureName:Employee +(conhost:$!{host.identifier} conhost:SYSTEM_HOST)",0,"Employee.lastName"))

#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", $!{})
    $person.put("seniorManagement", $employee.seniorManagement.selectValue)
    $person.put("phone", $
    $person.put("mobile", $
    $person.put("fax", $employee.fax)
    $person.put("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)


##How to cache the response for 500 seconds
$dotJSON.put("dotcache", 500)

##How to return the employee List as JSON
$dotJSON.put("employeeList", $resultList)

PATCH (update content via partial submit of only specified content fields)

The patch method performs a partial submit instead of requiring the entire object be sent, updating only the specific field data sent by the method.  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 -XPATCH http://localhost:8080/api/vtl/employees/37f93fcb-6124-46af-83b4-9ece6c1c5380 -H "Content-Type:application/json" -d '{"email": "","jobTitle": "updated Job title"}

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())
        $patchedMap.put($key, $bodyMap.get($key))

##Performs the patch submit of the data send in the form using the specified workflow action
#set($savedContent = $$patchedMap, $actionId));
$dotJSON.put("savedObject", $savedContent.getMap())


The delete .vtl merely sends the id of the content to a workflow 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 -XDELETE http://localhost:8080/api/vtl/employees/37f93fcb-6124-46af-83b4-9ece6c1c5380

delete.vtl code

#set($actionId = "777f1c6bc8")
#set($contentToDelete = $dotcontent.find($pathParam))
$$, $actionId);

The Lightweight Endpoints feature is intended for release in the dotCMS 5.1 version during the first quarter of 2019. dotCMS will continue to improve this and other features for both “presentation layer” and “headless” implementation clients to fulfill our “No Code or Low Code” initiative and continually position dotCMS as a leading hybrid implementation solution.

November 18, 2018

Recommended Reading

Benefits of a Multi-Tenant CMS and Why Global Brands Need to Consolidate

Maintaining or achieving a global presence requires effective use of resources, time and money. Single-tenant CMS solutions were once the go-to choices for enterprises to reach out to different market...

Headless CMS vs Hybrid CMS: How dotCMS Goes Beyond Headless

What’s the difference between a headless CMS and a hybrid CMS, and which one is best suited for an enterprise?

14 Benefits of Cloud Computing and Terminology Glossary to Get You Started

What is cloud computing, and what benefits does the cloud bring to brands who are entering into the IoT era?