Defining Custom Page Types in Sulu with XML Templates
Sulu defines custom content types as XML page templates in config/templates/pages, not YAML. A worked blog_post example, cache-clearing steps, rendering, JSON options, and pitfalls.
30 Sept 2025, 09:20 UTC

If you want a new content type in Sulu — say, a blog post with a title, excerpt, body text, and a featured image — the supported mechanism is an XML template, not a YAML file. Sulu reads template definitions from config/templates/pages/ at cache warm-up, builds the admin form from them, and stores content as documents in its content repository (PHPCR by default). One important correction up front: Sulu does not auto-generate a Doctrine entity per content type, and there is no generic /api/contents/<type> endpoint out of the box. Content is exposed through the website controllers you write, or through Sulu's headless/JSON options.
A working page template
Create config/templates/pages/blog_post.xml. The filename must match the template <key>. This example assumes Sulu 2.x:
<?xml version="1.0" ?>
<template xmlns="http://schemas.sulu.io/template/template"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/template-1.xsd">
<key>blog_post</key>
<view>pages/blog_post</view>
<controller>Sulu\Bundle\WebsiteBundle\Controller\DefaultController::indexAction</controller>
<cacheLifetime>2400</cacheLifetime>
<meta>
<title lang="en">Blog post</title>
</meta>
<properties>
<property name="title" type="text_line" mandatory="true">
<meta><title lang="en">Title</title></meta>
</property>
<property name="excerpt" type="text_area">
<meta><title lang="en">Excerpt</title></meta>
</property>
<property name="content" type="text_editor">
<meta><title lang="en">Content</title></meta>
</property>
<property name="featured_image" type="single_media_selection">
<meta><title lang="en">Featured image</title></meta>
</property>
</properties>
</template>Then clear the cache from the project root (needs write access to var/cache):
php bin/websiteconsole cache:clear
php bin/adminconsole cache:clearSulu splits its console into bin/adminconsole (admin environment) and bin/websiteconsole (website environment); clear both so the admin form and the website render pick up the template. Afterwards the "Blog post" type appears in the page-creation dialog under Content → Pages in the admin UI (/admin).
Rendering the content
The <view> points at a Twig template, here templates/pages/blog_post.html.twig. Property values arrive as view variables, and media selections resolve to arrays with format URLs:
<h1>{{ content.title }}</h1>
{% if content.featured_image is defined and content.featured_image %}
<img src="{{ content.featured_image.thumbnails['sulu-400x400'] }}" alt="">
{% endif %}
{{ content.content|raw }}For lists (e.g., all blog posts under a parent page), use Sulu's sulu_content_load / Smart Content features or query the DocumentManager in a custom controller. There is no per-type Doctrine entity; everything is a PageDocument distinguished by its template key.
Exposing JSON
Sulu does not ship a generic REST endpoint that returns arbitrary page content by type. Practical options: enable the headless capabilities of your Sulu version (newer 2.x releases can serialize page content as JSON via the website resolver), or write a small controller that loads documents via the DocumentManager and returns a JSON response. Verify whichever route you choose with curl -H "Accept: application/json" https://your-site/... and confirm the fields match the template.
Limits and common mistakes
- Nested data: repeatable groups use
<block>properties; deeply nested structures get awkward fast and may justify a custom entity with Sulu's list/form admin instead of a page template. - Wrong type names: property types are lowercase with underscores (
text_line,single_media_selection). A typo usually surfaces as an error on cache warm-up or a broken admin form. - Stale cache: template changes are invisible until both consoles' caches are cleared — the most common "my field doesn't show up" cause.
- Renaming the key: existing pages keep their old template key; renaming orphans them unless you migrate the stored documents.
Verifying the result
Run php bin/adminconsole sulu:content:types:list (or check the page-creation dialog) to confirm the template registered, create a test page in the admin, publish it, and load its URL on the website to confirm the Twig output renders each field. If the admin form errors, check var/log/admin/*.log for template parsing messages.
Note: exact console command names and headless JSON behavior vary between Sulu minor versions; confirm against the documentation for your installed version before relying on them.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.