Page API (Layout as a Service)

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

The Page API enables you to retrieve all the elements of any page in your dotCMS system. The elements may be retrieved in JSON format, or as fully rendered HTML; regardless of the format chosen, each element is returned separately within it's own JSON object, making it easy to retrieve and use all page elements with a single call.

Elements returned by the Page API include:

  • Page information including URL and SEO
  • Body Layout information as rows/columns
  • Template/Theme information
  • All Content Blocks, including what content types they can take
  • Visitor Context Information (if a client side call)
  • Rendered versions of the page and all content blocks (optional)

To see a simple example of the Page API's render mode in action, you can try a live variation of the Javascript single-page app featured in the example section:

Try Page API Rendering

Note: For additional information on the usage and benefits of the Page REST API and Layout-as-a-Service, please see the Layout as a Service (LaaS) post in the dotCMS blog.

Usage

Most calls to the Page API take the following form:

https://{server_address}/api/v1/page/{format}/{page_path}[?param1=val1&param2=val2]

Where:

  • {server_address} is replaced with the address of your dotCMS server.
  • {format} specifies the format to return each page element in.
    • Acceptable values are json or render.
  • {page_path} is replaced with the path to the page

Below is an example URL returns the page response from a page on the demo site. You must be logged in to see the response (email:admin@dotcms.com password:admin).

Parameters

The Page API accepts the following URL parameters:

ParameterValuesDescription
modeLIVE
WORKING
EDIT_MODE
Specifies live or drafted content.
• Note: EDIT_MODE adds the onNumberOfPages property to all returned contentlets that appear on more than one page, which displays the number of times this contentlet appears on page assets; this property is limited to this mode due to its expensive nature.
host_idSite IDThe ID of the page's site (if different from URL).
language_idLanguage IDThe ID of the language variant you want to retrieve.
com.dotmarketing.persona.idPersona IDThe ID of the persona variant you want to retrieve.
fireRulestrue
false
Indicates whether you want to fire the rules set on the page.
depth0-3Allows access to related content via the Relationship fields of contentlets on a page.
  • 0 (default) does not surface related content.
  • 1 surfaces immediate related contentlets.
  • 2 shows a second level of related content, including content related to the page asset itself.
  • 3 includes a third level of related content.
Other values throw an error.

Saving As Copy

The Page API also includes two endpoints capable of performing in-place content copying.

  • v1/page/copyContent saves a page with a copy of one of its included contentlets, allowing that page's version of the content to differ from the version of the content that appears elsewhere. This is automatically used in the dotCMS user interface when editing content as a copy in Edit Mode.
  • v1/page/{pageID}/_deepCopy duplicates an entire page and all contentlets therein.

To call copyContent directly, include a JSON payload with the following properties, as in the example below:

PropertyDescription
contentIdThe identifier of the contentlet to be copied.
pageIdThe identifier of the page containing the content.
containerIdThe identifier of the immediate Container holding the content.
personalizationThe identifier of a persona targeted by the content; for the default persona, use dot:default.
relationTypeA counter to distinguish between multiple instances of the same content, with default value of 1. (Example: On a page with three different contentlets, all three of them will return relationType: 1; if all three are the same contentlet, they will instead be 1, 2, and 3.)
treeOrderMaintains the dynamic relationship between pages, Containers, and content on the multi_tree table. Defaults to 0.
variantIdRelates to a forthcoming feature; set to DEFAULT.
```
curl -X 'PUT' \
  'https://local.dotcms.site:8443/api/v1/page/copyContent' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "contentId": "d99ef5e5a64327896b849e165b2eaadf",
  "pageId": "c7f8c15b7355ae088857e7220524a785",
  "containerId": "//demo.dotcms.com/application/containers/default/",
  "relationType": "1",
  "personalization": "dot:default",
  "variantId": "DEFAULT",
  "treeOrder": 0
}'

To call _deepCopy directly, no data payload is needed; just the page id as the first path parameter, and _deepCopy as the second:

curl -X 'PUT' \
  'https://local.dotcms.site:8443/api/v1/page/c7f8c15b7355ae088857e7220524a785/_deepcopy' \
  -H 'accept: application/json'

Language Version Availability

The Page API contains a REST endpoint that checks a page against available languages and determines whether or not a language version of the page exists for each.

The endpoint calls for a GET call to /api/v1/page/{PAGE_ID}/languages. The response includes a list of languages, each with a translated property that will display true if a version of the page exists in that language.

For example, when logged into the Demo Site, the following link will generate the subsequent JSON response:

https://demo.dotcms.com/api/v1/page/bec7b960-a8bf-4f14-a22b-0d94caf217f0/languages

{
  "entity": [
    {
      "country": "United States",
      "countryCode": "US",
      "id": 1,
      "isoCode": "en-us",
      "language": "English",
      "languageCode": "en",
      "translated": true
    },
    {
      "country": "Espana",
      "countryCode": "ES",
      "id": 2,
      "isoCode": "es-es",
      "language": "Espanol",
      "languageCode": "es",
      "translated": true
    }
  ],
  "errors":[],"i18nMessagesMap":{},"messages":[],
  "pagination":null,"permissions":[]
}

Vanity URLs

From dotCMS 24.06.06 onward, the Page API interacts fully with Vanity URLs.

When fetching a page behind a 301 and 302 Vanity URL redirect, the Page API will return an object in the following schema:

{
  "entity": {
    "canCreateTemplate": false,
    "containers": { },
    "numberContents": 0,
    "page": { },
    "site": { },
    "template": { },
    "vanityUrl": {
      "pattern": "/test1123",
      "vanityUrlId": "8bd957371150555c5cf9cf36c11791d8",
      "url": "/test1123",
      "siteId": "48190c8c-42c4-46af-8d1a-0cd5db894797",
      "languageId": 1,
      "forwardTo": "/destinations/costa-rica",
      "response" : 302,
      "order": 0,
      "temporaryRedirect": true,
      "permanentRedirect": false,
      "forward": false
    },
    "viewAs": {}
  },
  "errors":[],"i18nMessagesMap":{},"messages":[],
  "pagination":null,"permissions":[]
}

On the other hand, a 200 redirect — i.e., a forward — will instead return a standard Page API response object as though the redirect target had been the entered target. However, a vanityUrl property will still be present for analytical or troubleshooting purposes, in abbreviated form:

{
  ...
  "vanityUrl" : {
    "id" : "8bd957371150555c5cf9cf36c11791d8",
    "siteId" : "48190c8c-42c4-46af-8d1a-0cd5db894797",
    "url" : "/test1123",
    "forwardTo" : "/destinations/costa-rica",
    "response" : 200
  }
  ...
}

This allows greater control over rendering behaviors across a wide variety of routing patterns — especially in headless environments.

Examples

JSON Formatted Elements

The following call returns all the page elements of the dotCMS Demo site home page, with each page element being returned in JSON format:

(Remember: You must be logged into the Demo Site back end for this call to work.)

This call returns results which include all elements used to display the page, including the site, page, template, containers, content, and more. The following is a small sample of the information returned:

{
...
  "site" : {
    "lowIndexPriority" : false,
    "indexPolicyDependencies" : "DEFER",
    "default" : true,
    "aliases" : "localhost\n127.0.0.1",
    "inode" : "59bb8831-6706-4589-9ca0-ff74016e02b2",
    "hostname" : "demo.dotcms.com",
...
    "identifier" : "48190c8c-42c4-46af-8d1a-0cd5db894797",
    "modDate" : 1634235141702,
    "type" : "contentlet",
...
    "folder" : "SYSTEM_FOLDER",
    "archived" : false,
    "languageId" : 1,
    "working" : true,
    "modUser" : "dotcms.org.1",
...
  },
  "template" : {
    "iDate" : 1609946283995,
    "type" : "template",
    "owner" : "dotcms.org.1",
    "inode" : "1b90c278-0dfc-4ecc-ac5b-1827522ae469",
    "identifier" : "c6f813ef-f1f9-4759-a102-655bf441ec8e",
    "source" : "DB",
    "title" : "anonymous_layout_1609946283966",
    "friendlyName" : "",
    "modDate" : 1609946283996,
    "modUser" : "dotcms.org.1",
...

Fully Rendered Elements

The following call returns all the page elements of the dotCMS Demo Site home page, with each page element returned as fully rendered HTML, encapsulated within a JSON object:

(Remember: You must be logged into the Demo Site back end for this call to work.)

This call returns all of the same elements as the previous call, but returns displayable elements as fully rendered HTML. For an example, click the link above, and view the source of the rendered HTML page.

JavaScript Application Example

The following HTML code includes a full Javascript application which uses the Page REST API to pull and display the fully rendered contents of the dotCMS Demo Site index page. The app draws a page's slug from the url hash, fetches its content, and then resituates it into a simple template.

You can also try out a live variation on the page below, which has been extended to include dynamic, user-specified Page API calls.

<html>
    <head>
        <script>
            function getUrl(){
                var url = (window.location.hash.length==0)
                    ? "/index" //// PATH/SLUG GOES HERE
                    : window.location.hash.substr(1);
                if(!url.startsWith("/")){
                    url = "/" + url;
                }
                if(url.endsWith("/")){
                    url = url + "index";
                }
                window.location.hash = url;
                // HERE IS THE API ENDPOINT TO GET THE LAYOUT as a SERVICE back, 
                // e.g. https://demo.dotcms.com/api/v1/page/render/index
                return "https://demo.dotcms.com/api/v1/page/render" + url;
            }
            var url = getUrl();
            console.log("reading:"+url);

            function readJson(url, callback) {
                var req = new XMLHttpRequest();
                req.open("GET", url, true);
                req.setRequestHeader("Authorization", "Basic " + btoa("admin@dotcms.com:admin")); 
                req.onreadystatechange = function() {
                    if (req.readyState === 4 && req.status == "200") {
                        callback(req.responseText);
                    }
                }
                req.send();
            }

            window.onload = 
                readJson(url, function(text){
                    var data = JSON.parse(text).entity;
                    if(window.location.search.indexOf("raw=")>-1){
                        document.body.innerHTML=text;
                    }
                    else if(data.layout){
                        writeLayoutDocument(data);
                    }
                    else{
                        writeRawDocument(data);
                    }
                });

            function writeRawDocument(data){    
                document.body.innerHTML=data.page.map.rendered;
            }

            function writeLayoutDocument(data){ 
                var rows = data.layout.body.rows;
                var myPage = document.createElement('div');
                document.body.appendChild(myPage);
                myPage.style.cssText = 'border:2px solid #9AB3CE;margin:10px;padding:10px;background:#E0E9F6';
                myPage.innerHTML="reading from : <a href='" + url + "'>" + url + "</a>";

                if(data.layout.header){
                    var header = document.createElement('div');
                    header.innerHTML="<h1>Including Header</h1>";
                    header.style.cssText = 'border:2px solid #9AB3CE;margin:10px;padding:10px;background:#E0E9F6';
                    document.body.appendChild(header);
                }

                for(i=0;i<rows.length;i++){
                    var row = rows[i];
                    var columns = row.columns;
                    var thisRow = document.createElement('div');
                    thisRow.style.cssText = 'display:flex;flex-direction:row;justify-content:space-between';
                    document.body.appendChild(thisRow);

                    for(j=0;j<columns.length;j++){
                        var column = columns[j];
                        var containers = column.containers;
                        var thisColumn = document.createElement('div');
                        thisColumn.style.cssText = 'flex-basis:' + (column.widthPercent-0) + '%;border:2px solid #9AB3CE;margin:10px;padding:10px;background:#EEF5F9';
                        thisRow.appendChild(thisColumn);

                        for(k=0;k<containers.length;k++){
                            var containerId = containers[k].identifier;
                            var containerUUID = containers[k].uuid;

                            var thisContainerInfo = document.createElement('div');
                            thisContainerInfo.style.cssText = "color:gray;text-align:right;"
                            thisColumn.appendChild(thisContainerInfo);
                            thisContainerInfo.innerHTML="{row:" + (i+1) + ",column:" + (j+1)+ ",width:" + column.widthPercent + "%}";
                            var thisContainer = document.createElement('div');
                            thisContainer.style.cssText = 'border:3px solid #9AB3CE;background:#ffffff;margin:10px;padding:10px;';
                            thisColumn.appendChild(thisContainer);

                            // add in the pre-rendered container content
                            console.log("rendered", containerUUID)
                            thisContainer.innerHTML=data.containers[ containerId ].rendered["uuid-" + containerUUID ];
                        }
                    }
                }
                if(data.layout.footer){
                    var footer = document.createElement('div');
                    footer.innerHTML="<h1>Including Footer</h1>";
                    footer.style.cssText = 'border:2px solid #9AB3CE;margin:10px;padding:10px;background:#E0E9F6';
                    document.body.appendChild(footer);
                }
            }
        </script>
        <base href="https://demo.dotcms.com">
    </head>
    <body></body>
</html>

Feel free to try out a few Page API calls for yourself!

On this page

×

We Dig Feedback

Selected excerpt:

×