This project demonstrates how to build a DotCMS webapp with Laravel using the DotCMS PHP SDK. It provides a complete example of rendering DotCMS pages within a Laravel application, including layouts, containers, and content types.
This integration allows you to:
dotcms-laravel/
├── app/
│ ├── Http/
│ │ └── Controllers/
│ │ └── AppController.php # Handles all DotCMS page requests
│ ├── Providers/
│ │ ├── AppServiceProvider.php
│ │ └── DotCMSServiceProvider.php # Registers DotCMS services
│ └── Helpers/
│ └── DotCmsHelpers.php # Helper functions for DotCMS rendering
├── resources/
│ ├── css/
│ │ └── app.css # Styles including grid system
│ ├── js/
│ └── views/
│ ├── layouts/
│ │ ├── app.blade.php # Base layout
│ │ ├── header.blade.php # Header component
│ │ └── container.blade.php # Container template
│ ├── dotcms/
│ │ ├── components/
│ │ │ └── navigation.blade.php # Navigation component
│ │ └── content-types/ # Content type templates
│ │ ├── activity.blade.php
│ │ ├── banner.blade.php
│ │ └── product.blade.php
│ └── page.blade.php # Main page template
└── routes/
└── web.php # Route definitions including catch-all routecomposer create-project laravel/laravel my-dotcms-project
cd my-dotcms-projectThis command creates a fresh Laravel installation, which serves as the foundation for your DotCMS-integrated application.
composer require dotcms/php-sdkThis adds the official DotCMS PHP SDK to your project, providing the necessary methods to interact with DotCMS APIs.
.env:DOTCMS_HOST=https://demo.dotcms.com
DOTCMS_API_KEY=your-api-key-hereThese environment variables define the connection to your DotCMS instance, allowing the SDK to authenticate and make API calls.
If you want to test the Laravel example with a local version of the PHP SDK (for development or testing new changes), you can use the composer.dev.json configuration:
cd examples/dotcms-laravelcomposer install, remove the vendor directory:rm -rf vendorCOMPOSER=composer.dev.json composer installThis will use the local SDK from the parent directory instead of the published package version.
All the configuration described below is already implemented in this example project. The following sections explain the key components and how they work together to integrate DotCMS with Laravel.
Create a service provider to register the DotCMS client in app/Providers/DotCMSServiceProvider.php:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Dotcms\PhpSdk\Config\Config;
use Dotcms\PhpSdk\DotCMSClient;
class DotCMSServiceProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
$this->app->singleton(DotCMSClient::class, function ($app) {
$config = new Config(
host: env('DOTCMS_HOST', 'https://demo.dotcms.com'),
apiKey: env('DOTCMS_API_KEY', '')
);
return new DotCMSClient($config);
});
}
/**
* Bootstrap services.
*/
public function boot(): void
{
//
}
}This service provider:
Register the service provider in app/Providers/AppServiceProvider.php:
$this->app->register(DotCMSServiceProvider::class);And in bootstrap/providers.php:
App\Providers\DotCMSServiceProvider::class,These registrations ensure the DotCMS service provider is loaded during application bootstrap.
Create helper functions for DotCMS rendering in app/Helpers/DotCmsHelpers.php. This implementation leverages the helper utilities provided by the PHP SDK:
<?php
namespace App\Helpers;
use Dotcms\PhpSdk\Utils\DotCmsHelper;
use Dotcms\PhpSdk\Model\Content\Contentlet;
class DotCmsHelpers
{
/**
* Generate HTML attributes from an associative array
*
* @param array $attributes
* @return string
*/
public function htmlAttr($attributes)
{
return DotCmsHelper::htmlAttributes($attributes);
}
/**
* Generate HTML based on contentlet properties
*
* @param Contentlet $content
* @return string
*/
public function generateHtmlBasedOnProperty(Contentlet $content)
{
if (empty($content)) {
return '';
}
// Check if we have a template to render
$contentType = $content->contentType;
if ($contentType) {
$viewPath = 'dotcms.content-types.' . strtolower($contentType);
if (view()->exists($viewPath)) {
return view($viewPath, ['content' => $content])->render();
}
}
// Fall back to the SDK simple HTML renderer
return DotCmsHelper::simpleContentHtml($content->jsonSerialize());
}
}The DotCMSHelpers class provides two key functions, all leveraging the SDK's DotCmsHelper utility class:
htmlAttr: Safely generates HTML attributes from arrays, handling special cases like boolean attributesgenerateHtmlBasedOnProperty: Renders content intelligently by looking for type-specific templates or falling back to the SDK's default rendererCreate a service provider to share the helpers with all views in app/Providers/DotCmsHelpersServiceProvider.php:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use App\Helpers\DotCmsHelpers;
class DotCmsHelpersServiceProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
//
}
/**
* Bootstrap services.
*/
public function boot(): void
{
// Share the DotCmsHelpers with all views
View::share('dotCmsHelpers', new DotCmsHelpers());
}
}This service provider makes the helper functions available in all Blade templates via the $dotCmsHelpers variable, ensuring a consistent interface for templating.
Register the helper service provider in app/Providers/AppServiceProvider.php:
$this->app->register(DotCmsHelpersServiceProvider::class);In routes/web.php, add a catch-all route to handle DotCMS pages:
// Catch-all route for rendering DotCMS pages
// This should only trigger for routes that might be DotCMS pages
// Static assets should be served directly from public directory
Route::fallback([AppController::class, 'index'])->where('fallbackPlaceholder', '^(?!.*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)).*$');This route configuration:
Create a controller to handle DotCMS page requests in app/Http/Controllers/AppController.php:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Dotcms\PhpSdk\DotCMSClient;
class AppController extends Controller
{
/**
* The DotCMS client instance.
*/
protected $dotCMSClient;
/**
* Create a new controller instance.
*/
public function __construct(DotCMSClient $dotCMSClient)
{
$this->dotCMSClient = $dotCMSClient;
}
/**
* Handle the SPA rendering with dotCMS page data
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request)
{
try {
// Get the current path from the request
$path = $request->path();
$path = $path === '/' ? '/' : '/' . $path;
// Create a page request for the current path
$pageRequest = $this->dotCMSClient->createPageRequest($path, 'json');
// Get the page data
$pageAsset = $this->dotCMSClient->getPage($pageRequest);
// Create a navigation request with depth=2
$navRequest = $this->dotCMSClient->createNavigationRequest('/', 2);
// Get the navigation
$nav = $this->dotCMSClient->getNavigation($navRequest);
// Check for entity wrapper in the response
if (isset($pageAsset->entity)) {
// Some dotCMS versions return data in an 'entity' wrapper
$page = $pageAsset;
} else {
// Standard structure already expected by our templates
$page = $pageAsset;
}
// Pass the data to the view
return view('page', [
'pageAsset' => $page,
'navigation' => $nav
]);
} catch (\Exception $e) {
// Log the error
Log::error('dotCMS API Error: ' . $e->getMessage());
// Rethrow the exception to let Laravel handle it
throw $e;
}
}
}The AppController:
Create the necessary templates to render DotCMS content:
resources/views/layouts/app.blade.php):#<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@yield('title', 'DotCMS Laravel')</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>">
@section('stylesheets')
@vite(['resources/css/app.css', 'resources/js/app.js'])
@show
</head>
<body>
@include('layouts.header')
@yield('content')
</body>
</html>This main layout:
resources/views/page.blade.php):#@extends('layouts.app')
@section('title', isset($pageAsset->page->title) ? $pageAsset->page->title : 'DotCMS Laravel')
@section('content')
{{-- Page Content --}}
@if(isset($pageAsset->layout) && isset($pageAsset->layout->body) && isset($pageAsset->layout->body['rows']))
@foreach($pageAsset->layout->body['rows'] as $row)
<div class="container">
<div data-dot-object="row" class="row{{ isset($row['styleClass']) ? ' ' . $row['styleClass'] : '' }}">
@if(isset($row['columns']) && !empty($row['columns']))
@foreach($row['columns'] as $column)
@php
$startClass = 'col-start-' . ($column['leftOffset'] ?? 0);
$endClass = 'col-end-' . (($column['width'] ?? 12) + ($column['leftOffset'] ?? 0));
@endphp
<div data-dot-object="column" class="{{ $startClass }} {{ $endClass }}{{ isset($column['styleClass']) ? ' ' . $column['styleClass'] : '' }}">
@if(isset($column['containers']) && !empty($column['containers']))
@foreach($column['containers'] as $container)
@include('layouts.container', [
'container' => $container,
'containers' => $pageAsset->containers ?? []
])
@endforeach
@endif
</div>
@endforeach
@endif
</div>
</div>
@endforeach
@else
<div class="container">
<div class="row">
<div class="col-start-1 col-end-13">
<div class="alert alert-warning">
No layout found
</div>
</div>
</div>
</div>
@endif
@endsectionThis page template:
resources/views/layouts/container.blade.php):#@php
$containerAttrs = [
'data-dot-object' => 'container',
'data-dot-identifier' => $container->identifier,
'data-dot-accept-types' => $container->acceptTypes,
'data-max-contentlets' => $container->maxContentlets,
'data-dot-uuid' => $container->uuid
];
@endphp
<div {!! $dotCmsHelpers->htmlAttr($containerAttrs) !!}>
@foreach($container->contentlets as $content)
@php
$contentAttrs = [
'data-dot-object' => 'contentlet',
'data-dot-identifier' => $content->identifier,
'data-dot-basetype' => $content->baseType,
'data-dot-title' => $content->widgetTitle ?? $content->title,
'data-dot-inode' => $content->inode,
'data-dot-type' => $content->contentType,
'data-dot-container' => json_encode([
'acceptTypes' => $container->acceptTypes,
'identifier' => $container->identifier,
'maxContentlets' => $container->maxContentlets,
'variantId' => $container->variantId,
'uuid' => $container->uuid
])
];
@endphp
<div {!! $dotCmsHelpers->htmlAttr($contentAttrs) !!}>
{!! $dotCmsHelpers->generateHtmlBasedOnProperty($content) !!}
</div>
@endforeach
</div>The container template:
php artisan serveThis command starts Laravel's built-in development server on port 8000, providing a quick way to test your application.
http://localhost:8000 in your browser. The application will fetch and render pages from your DotCMS instance.To enable the Universal Visual Editor (UVE) for in-context editing:
npm install @dotcms/uveresources/js/app.js to initialize UVE:import {createUVESubscription} from '@dotcms/uve';
import './bootstrap';
try {
createUVESubscription('changes', (changes) => {
window.location.reload();
});
} catch (error) {
console.warn('dotUVE is not available, you might experience issues with the the Universal Visual Editor', error);
}This setup enables real-time updates when content is edited through the Universal Visual Editor.
AppController::index().DotCMSClient to fetch the page from DotCMS.createUVESubscription is to subscribe to the pages changes that the user performs inside the UVE.sequenceDiagram
participant User
participant Laravel as Laravel Router
participant Controller as AppController
participant Client as DotCMSClient
participant DotCMS as DotCMS API
participant Blade as Blade Templates
User->>Laravel: Request URL
Laravel->>Controller: Route to index() method
Controller->>Client: createPageRequest(path)
Client->>DotCMS: API Request
DotCMS-->>Client: Return PageAsset
Client-->>Controller: Return PageAsset
Controller->>Blade: Render with page data
Blade->>Blade: Process with DotCmsHelpers
Blade-->>User: Return rendered HTMLThis sequence diagram illustrates the complete request flow from user browser through Laravel, DotCMS, and back to the browser as rendered HTML.
The mapping between content types and templates is implemented in the generateHtmlBasedOnProperty method in app/Helpers/DotCmsHelpers.php:
public function generateHtmlBasedOnProperty($content)
{
if (empty($content)) {
return '';
}
// Check if we have a template to render
$contentType = $content['contentType'] ?? '';
if ($contentType) {
$viewPath = 'dotcms.content-types.' . strtolower($contentType);
if (view()->exists($viewPath)) {
return view($viewPath, ['content' => $content])->render();
}
}
// Default rendering with title
$title = $content['title'] ?? $content['name'] ?? 'No Title';
return '<div class="content-wrapper"><h3>' . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . '</h3></div>';
}This method:
$content['contentType'])dotcms.content-types.[content-type-name]view()->exists()The method is called from the container template (resources/views/layouts/container.blade.php):
<div {!! $dotCmsHelpers->htmlAttr($contentAttrs) !!}>
{!! $dotCmsHelpers->generateHtmlBasedOnProperty($content) !!}
</div>Each DotCMS content type is mapped to a corresponding Blade template:
Activity content type → resources/views/dotcms/content-types/activity.blade.php
Banner content type → resources/views/dotcms/content-types/banner.blade.php
Product content type → resources/views/dotcms/content-types/product.blade.php
To add support for a new content type:
resources/views/dotcms/content-types/your-content-type.blade.phpgenerateHtmlBasedOnProperty method in DotCmsHelpers.php will find your template based on the content type nameThe project includes a basic grid system and utility classes in resources/css/app.css. It uses Tailwind CSS for styling components.
This project is open-sourced software licensed under the MIT license.
Found an issue with this documentation? View the source