This guide walks you through rendering dotCMS pages using PHP and Twig, a popular templating engine. We will explore retrieving data from the dotCMS Page API and displaying it through Twig templates, providing a clean separation of logic and presentation.
Prerequisites
A PHP environment (PHP 8 or higher) with Composer installed.
Access to a dotCMS server with an API token.
Basic knowledge of Twig, PHP, and HTML.
Project Folder Structure
Organize your project with the following structure:
.
├── .env # Environment variables (API token)
├── composer.json # Composer dependencies
├── index.php # Main PHP entry point
├── templates # Twig templates folder
│ ├── base.twig # Base template
│ └── content-types # Folder for content type templates
│ └── activity.twig # Twig template for Activity content type
│ └── banner.twig # Twig template for Banner content type
│ └── product.twig # Twig template for Product content type
├── style.css # CSS for layout
└── README.md # Documentation (optional)
Get Your API Token
Before starting, you need to generate an API token from dotCMS. Follow the instructions in the dotCMS API Authentication Documentation and store it in your .env file:
DOTCMS_API_TOKEN=your_api_token_here
Step 1: Initializing Composer and Installing Twig
Composer is a dependency management tool for PHP, allowing you to easily manage and install libraries. Before installing Twig, you need to initialize Composer in your project. Run the following command in the root of your project to generate a composer.json file automatically:
composer init
Follow the prompts to set up the project metadata. Once initialized, you can install Twig using:
composer require twig/twig
This will install Twig and update the composer.json and composer.lock files automatically.
Step 2: Rendering the Page Title with Twig
First, set up index.php to initialize Twig and fetch data from the dotCMS API:
<?php
require_once DIR . '/vendor/autoload.php';
// Load environment variables
$envFile = DIR . '/.env';
$_ENV = file_exists($envFile) ? parse_ini_file($envFile) : [];
// Set up Twig
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/templates');
$twig = new \Twig\Environment($loader);
// Fetch page data
try {
$token = $_ENV['DOTCMS_API_TOKEN'] ?? throw new RuntimeException('API token missing');
$url = 'https://demo.dotcms.com/api/v1/page/json/index';
$context = stream_context_create([
'http' => [
'header' => "Authorization: Bearer $token",
'method' => 'GET',
'ignore_errors' => true,
]
]);
$response = file_get_contents($url, false, $context);
$data = json_decode($response, true, JSON_THROW_ON_ERROR)['entity'];
$pageTitle = $data['page']['friendlyName'];
} catch (Throwable $e) {
die("Error loading page: " . $e->getMessage());
}
// Render Twig template
echo $twig->render('base.twig', ['pageTitle' => $pageTitle]);
This section explains how to initialize Twig in a PHP application and fetch data from the dotCMS API.
Require the Twig autoload: The line require_once DIR . '/vendor/autoload.php'; loads all the dependencies installed via Composer, including Twig.
Load environment variables: Using parse_ini_file, the .env file is read to get the API token needed to authenticate with the dotCMS API.
Set up Twig: The Twig\Loader\FilesystemLoader specifies where the template files (.twig) are located. Then, the Twig\Environment creates the engine to process these templates.
Fetch page data: A GET request is sent to the dotCMS Page API using file_get_contents. The API token is included in the request headers for authentication.
Error handling: If anything goes wrong (e.g., no token or failed request), the script will terminate with an error message.
Render the template: The data from the API, specifically the pageTitle, is passed to the Twig template (base.twig) for rendering.
Step 3: Creating the Twig Template
Inside the templates folder, create base.twig to display the page title and a placeholder message.
templates/base.twig:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ pageTitle }}</title>
</head>
<body>
<header>
<h1>{{ pageTitle }}</h1>
</header>
<main>
<p>Your dotCMS page is being set up!</p>
</main>
</body>
</html>
When you run the project, the page title fetched from the dotCMS API will be displayed dynamically.
Running the Project Locally
Start a development server using the following command:
php -S localhost:8000
Visit http://localhost:8000 in your browser to see the rendered page.
Understanding the dotCMS Layout System
The dotCMS layout system defines how rows, columns, and containers are structured on the page. The layout system follows a 12-column CSS grid system, making it flexible and easy to customize.
The dotCMS Page API response contains three key parts:
Page Properties (entity.page): Metadata such as the page title and description.
Layout Information (entity.layout.body): Contains rows and columns information.
Containers and Contentlets (entity.containers): Content blocks that are displayed on the page.
Step 4: Rendering Rows, Columns, and Content Dynamically
We can enhance the index.php to pass the rows, columns, and contentlets to the Twig template:
Updating index.php
// Extract layout data
$rows = $data['layout']['body']['rows'] ?? [];
$containers = $data['containers'] ?? [];
// Render the template with layout information
echo $twig->render('base.twig', [
'pageTitle' => $pageTitle,
'rows' => $rows,
'containers' => $containers
]);
Updating the Twig Template
Modify base.twig to loop through rows and columns dynamically:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ pageTitle }}</title>
</head>
<body>
<header>
<h1>{{ pageTitle }}</h1>
</header>
<main>
{% for row in rows %}
<div class="row">
{% for column in row.columns %}
<div class="col-start-{{ column.leftOffset }} col-end-{{ column.leftOffset + column.width }}">
{% for container in column.containers %}
{% set containerData = containers[container.identifier].contentlets['uuid-' ~ container.uuid] ?? [] %}
{% for contentlet in containerData %}
{% include 'content-types/' ~ contentlet.contentType|lower ~ '.twig' with { 'contentlet': contentlet } %}
{% endfor %}
{% endfor %}
</div>
{% endfor %}
</div>
{% endfor %}
</main>
</body>
</html>
This template iterates through rows and columns, dynamically including a separate template for each content type.
Step 5: Creating Content Type Twig Templates
For each content type, create a dedicated Twig template under templates/content-types. Below are examples for common content types you might encounter:
templates/content-types/activity.twig:
<article class="activity">
<h2>{{ contentlet.title }}</h2>
<p>{{ contentlet.description }}</p>
</article>
templates/content-types/banner.twig:
<article class="banner">
<img src="{{ contentlet.image }}" alt="{{ contentlet.title }}">
<h2>{{ contentlet.title }}</h2>
<p>{{ contentlet.description }}</p>
</article>
templates/content-types/product.twig:
<article class="product">
<h3>{{ contentlet.title }}</h3>
<p>Price: {{ contentlet.price }}</p>
<a href="{{ contentlet.url }}">Buy Now</a>
</article>
You can create additional templates for other content types as needed, ensuring each one handles the specific fields and design required for its respective content type.
Step 6: Customizing Content Rendering by Type
The base.twig template includes the corresponding content type template using:
{% include 'content-types/' ~ contentlet.contentType|lower ~ '.twig' with { 'contentlet': contentlet } %}
This line dynamically selects the correct template based on the contentType property of the contentlet. For example, if the content type is Activity, it includes activity.twig.
You can now render contentlets differently based on their type, providing flexibility and clean separation of concerns.
Conclusion
By using Twig with PHP and organizing content type templates, you can render dotCMS pages cleanly while keeping the logic and presentation separate. This approach scales easily, making it simple to add new content types as needed. While production-ready applications would require more considerations, this guide demonstrates how flexible and tech-agnostic the dotCMS Universal Visual Editor is. Happy coding!