Introduction

The Laravel CMS JSON frontend API follows the JSON:API standard documented at jsonapi.org and is available at (replace “mydomain.tld” with your own one):

http://mydomain.tld/api/cms/pages

The pages endpoint will return items from the page tree as well as related shared content elements depending on parameters added.

Available properties

Page properties

The available page properties are:

{
    "type": "pages",
    "id": "1",
    "attributes": {
        "parent_id": null,
        "lang": "",
        "slug": "",
        "name": "Home",
        "title": "Home | Laravel CMS",
        "tag": "root",
        "to": "",
        "domain": "mydomain.tld",
        "cache": 5,
        "data": [
            {
                "text": "Welcome to Laravel CMS",
                "type": "cms::heading"
            }
        ],
        "meta": {
            "cms::meta": {
                "text": "Laravel CMS is outstanding",
                "type": "cms::meta"
            }
        },
        "config": null,
        "createdAt": "2023-03-12T16:06:26.000000Z",
        "updatedAt": "2023-03-12T16:06:26.000000Z"
    }
}
parent_id
ID of the parent page
lang
ISO language code, either two letters in lower case (e.g. “en”) or five characters for country specific languages (e.g. “en_US”)
slug
URL segment of the page (must not contain any slashes)
name
Short page name for navigation
title
Page title like shown in the browser
tag
Arbitrary name which can be used for filtering to get a specific page
to
URL to the target page in case the page is redirecting to another page
domain
Domain name the (root) page is responsible for
cache
How long the returned response (and therefore the generated page can be cached
meta
Set of arbitrary page meta data that should be part of the page head
data
List of arbitrary page content elements that should be part of the page body
config
Arbitrary key/value pairs with page configuation
createdAt
ISO date/time when the page was created
updatedAt
ISO date/time when the page was last modified

Shared content properties

The available properties of content shared between pages are:

{
    "type": "contents",
    "id": "0186d692-be0b-798c-9450-0a676209b7a6",
    "attributes": {
        "lang": "",
        "data": {
            "text": "Welcome to Laravel CMS",
            "type": "cms::heading"
        },
        "createdAt": "2023-03-12T16:06:26.000000Z"
    }
}
lang
ISO language code, either two letters in lower case (e.g. “en”) or five characters for country specific languages (e.g. “en_US”)
data
Arbitrary content element that should be part of the page body
createdAt
ISO date/time when the content element was created

URL parameters

Filter results

To limit the returned items exactly to the ones you want, the JSON API supports one or more filter parameters:

domain
Filters the pages by domain name which must be set at least in the root page in the domain property
tag
Returns only pages (or one page) where the passed value is set in the tag property of the page item
lang
In case of multi-language page trees, this parameter limits the pages to the specified language set in the lang property of the page item

To get the page tagged with root for the domain mydomain.tld in English, use:

http://mydomain.tld/api/cms/pages?filter[tag]=root&filter[domain]=mydomain.tld&filter[lang]=en

Include resources

When including related resources, you can get all data you need to render the page including the navigation in one request. The available related resources are:

contents
List of shared content elements for the requested page (paginated if more than 50 items)
parent
Parent page item
ancestors
All parent pages up to the root page
children
List of direct child pages for the requested page (paginated if more than 15 items)
subtree
Tree of sub-pages up to three levels deep for building a mega-menu

To get the page tagged with blog including its ancestors and shared content elements use:

http://mydomain.tld/api/cms/pages?filter[tag]=blog&include=ancestors,contents

There are detailed examples for the most often used requests available:

Pagination

You can paginate through the results by adding the page parameter to the /api/cms/pages URL. The supported values are:

number
Number of the slice that should be fetched starting from “1” up to the total number of available pages (see the pagination respone for details)
size
Number of items that should be fetched with a minimum value of “1” and a maximum value of “100”. The default values are “15” for pages and “50” for contents

To get item 25 to 50 from the pages endpoint use:

http://mydomain.tld/api/cms/pages?page[number]=2&page[size]=25

This can be combined with filter and include parameters too:

http://mydomain.tld/api/cms/pages?filter[lang]=en&include=contents&page[number]=2&page[size]=25

In the last case, use the link instead of constructing the URL yourself!

Sparse fields

Most often, you don’t need all page or shared content properties and you can reduce the amount of data returned in the response by using the fields parameter. The requested fields can be limited for pages and shared content elements separately and the property names must be concatenated by comma.

To retrieve the slug and lang of the root pages only and the data property of the shared content elements, use:

http://mydomain.tld/api/cms/pages?include=contents&fields[pages]=slug,lang&fields[contents]=data

Then, the attributes of the returned pages in the data section will contain only:

"data": [
    {
        "type": "pages",
        "id": "1",
        "attributes": {
            "lang": "",
            "slug": ""
        },
        "links": {
            "self": "http:\/\/mydomain.tld\/api\/cms\/pages\/1"
        }
    }
]

The type and id of each item is always returned outside the attributes and can’t be skipped!

Responses

In the JSON-encoded response, there are three sections which are important:

Meta

Base URL

The meta section always contains the baseurl key which is the base URL to all files/images referenced by the page or the shared content elements. Typically, you will see this in most responses:

"meta": {
    "baseurl": "http:\/\/mydomain.tld\/storage\/"
}

In Laravel, you can change the base URL in the ./config/filesystems.php file where you need to change the url setting for the disk Laravel CMS is using (public by default).

Paged results

Responses which returns a collection of pages (/api/cms/pages), you will also notice a page key in the meta section which contains the pagination information:

"meta": {
    "page": {
        "currentPage": 1,
        "from": 1,
        "lastPage": 1,
        "perPage": 15,
        "to": 1,
        "total": 1
    }
}

Important key/value pairs are:

currentPage
Page number of the current page (starts with “1”)
lastPage
Page number of the last page available when using the same perPage value. Minimum value is “1”, the maximum value is “100”
perPage
Maximum number of items returned in one response which is the passed page[size] value. The minimum value is “1”, the maximum value is “100” and the default values are “15” for pages and “50” for contents
total
Total number of available pages when using the same size value

The links section in the JSON API response is always included and contains the self link which would return the same response again:

"links": {
    "self": "http:\/\/mydomain.tld\/api\/cms\/pages\/1"
},

Thus, you can always use the links to fetch data and don’t have to construct the links yourself!

Data

The data section of the JSON:API response contains either a single resource (in case of e.g. /api/cms/pages/1) or a collection of resources (for /api/cms/pages).

Single item

Using a request which returns a single page, then the response is like:

"data": {
    "type": "pages",
    "id": "1",
    "attributes": {
        "parent_id": null,
        "lang": "",
        "slug": "",
        "name": "Home",
        "title": "Home | Laravel CMS",
        "tag": "root",
        "to": "",
        "domain": "mydomain.tld",
        "has": true,
        "cache": 5,
        "data": [
            {
                "text": "Welcome to Laravel CMS",
                "type": "cms::heading"
            }
        ],
        "meta": {
            "cms::meta": {
                "text": "Laravel CMS is outstanding",
                "type": "cms::meta"
            }
        },
        "config": null,
        "createdAt": "2023-05-01T09:36:30.000000Z",
        "updatedAt": "2023-05-01T09:36:30.000000Z"
    },
    "relationships": {
        "contents": {
            "data": [
                {
                    "type": "contents",
                    "id": "0187d6ab-b76d-75ee-8830-ab00b4259aa5"
                }
            ]
        }
    },
    "links": {
        "self": "http:\/\/localhost:8000\/api\/cms\/pages\/1"
    }
},

The data section contains exactly one object with type and id properties which uniquely identifies the resource. Within the attributes part, the page properties are listed like shown above but could be also less if you’ve requested only specific fields. In the links part, the self URL to retrieve the same page data is included. The relationships part is described in the relationships section of this document.

Multiple items

For request returning multiple items, the data section will be similar to:

"data": [
    {
        "type": "pages",
        "id": "1",
        "attributes": {
            "parent_id": null,
            "lang": "",
            "slug": "",
            "name": "Home",
            "title": "Home | Laravel CMS",
            "tag": "root",
            "to": "",
            "domain": "mydomain.tld",
            "has": true,
            "cache": 5,
            "data": [
                {
                    "text": "Welcome to Laravel CMS",
                    "type": "cms::heading"
                }
            ],
            "meta": {
                "cms::meta": {
                    "text": "Laravel CMS is outstanding",
                    "type": "cms::meta"
                }
            },
            "config": null,
            "createdAt": "2023-05-01T09:36:30.000000Z",
            "updatedAt": "2023-05-01T09:36:30.000000Z"
        },
        "relationships": {
            "contents": {
                "data": [
                    {
                        "type": "contents",
                        "id": "0187d6ab-b76d-75ee-8830-ab00b4259aa5"
                    }
                ]
            }
        },
        "links": {
            "self": "http:\/\/localhost:8000\/api\/cms\/pages\/1"
        }
    },
    // ...
],

It’s the same like for responses returning single resources but the data section contains a list of page items.

Relationships

If you use the include parameter to get related resources in the same request there will be a key for each related resource below relationships.

For a request which should include the parent page, ancestor pages, child pages, the page subtree and the contents like:

http://mydomain.tld/api/cms/pages/1?include=parent,ancestors,children,subtree,contents

Then, the relationships section will contain:

"relationships": {
    "contents": {
        "data": [
            {
                "type": "contents",
                "id": "0187d6ab-b76d-75ee-8830-ab00b4259aa5"
            }
        ]
    },
    "parent": {
        "data": null
    },
    "children": {
        "data": [
            {
                "type": "pages",
                "id": "2"
            },
            {
                "type": "pages",
                "id": "4"
            },
            {
                "type": "pages",
                "id": "5"
            }
        ]
    },
    "ancestors": {
        "data": []
    },
    "subtree": {
        "data": [
            {
                "type": "pages",
                "id": "2"
            },
            {
                "type": "pages",
                "id": "3"
            },
            {
                "type": "pages",
                "id": "4"
            },
            {
                "type": "pages",
                "id": "5"
            }
        ]
    }
},

Each key in the relationships part will a reference to a single item (like for parent) or references to multiple items in their data sections. The items themselves will be part of the included section of the returned response. In case of the root page, the parent/data key can also be NULL because there’s no parent page for the root page any more:

"relationships": {
    "parent": {
        "data": null
    }
},

Included

The included section of each JSON API response is only available if you’ve added the include parameter to the URL, e.g. /api/cms/pages/1?include=contents. In that case the relationships/contents/data part contains the list of references:

{
    "type": "pages",
    "id": "1",
    "attributes": {
        "name": "Home",
        "title": "Home | Laravel CMS",
        "tag": "root",
        "more keys": "..."
    },
    "relationships": {
        "contents": {
            "data": [
                {
                    "type": "contents",
                    "id": "0186d692-be0b-798c-9450-0a676209b7a6"
                }
            ]
        }
    }
}

And the included section for that response then contains:

"included": [
    {
        "type": "contents",
        "id": "0186d692-be0b-798c-9450-0a676209b7a6",
        "attributes": {
            "lang": "",
            "data": {
                "text": "Welcome to Laravel CMS",
                "type": "cms::heading"
            },
            "created_at": "2023-03-12T16:06:26.000000Z"
        }
    }
]

It consists of a flat list of page or shared content items identified by their type and id values. You must now match the type and ID within the relationships/contents section with the type and ID within the included section.

Error handling

Errors can and will occur sooner or later. The JSON:API standard like every REST protocol uses the HTTP status codes to signal error conditions. Used HTTP status codes are:

  • 2xx : Successful operation
    • 200 : Operation was performed successfully
    • 201 : Resource has been created
  • 4xx : Bad request
    • 401 : Authentication required
    • 403 : Operation is forbidden/unsupported
    • 404 : The resource wasn’t found
  • 5xx : Internal server error
    • 500 : A non-recoverable error occurred
    • 501 : Operation not implemented

Also, the JSON API standard specifies an “errors” section in the JSON response that can contain error hints for one or more operations:

{
    "errors": [
        {
            "title": "No product with ID 1 available",
            "detail": "<stack trace where the error occured>"
        },
        ...
    ]
}

Each error item contains a “title” attribute that contains the error message for the user and the “detail” attribute including the stack trace for developers. You should show the error details because they are only helpful for developers:

const promise = fetch('/api/cms/pages?...', {
    method: 'GET',
    credentials: 'same-origin',
}).then(response => {
    if(!response.ok) {
        throw new Error(response.statusText)
    }
    return response.json();
}).then(result => {
    if(result.errors) {
        throw result.errors
    }
    return result
}).catch(err => {
    console.error(err)
})

Comments