Let Editors Own Listing Pages: Sulu XML Templates and Smart Content
Sulu's XML page templates plus the Smart Content type let editors control dynamic listing pages — filters, tags, limits — without a developer touching a query. Here's a working news index example and the trade-offs.
19 Sept 2026, 18:50 UTC

Every content-driven site eventually gets the same request: "Can we change which articles show up on the news page without asking a developer?" If your listing pages are backed by hardcoded repository queries, the answer is always a deploy. Sulu offers a cleaner split: define the page structure in an XML template, expose a Smart Content field, and let editors control the filters. The developer ships the template once; the editorial team tunes the listing forever after.
The thesis: queries belong in the admin UI, not the controller
Sulu is a Symfony-based CMS, but unlike a typical Symfony app you rarely write controllers for content pages. Instead, each page type is declared in an XML template file (conventionally under config/templates/pages). The XML declares typed properties — text_line, text_editor, media_selection, block, and others — and Sulu generates the admin form automatically from that definition. Rendering happens in Twig, where you read values off the content structure.
Smart Content is the property type that changes the game for listing pages. Instead of a developer writing "fetch the 10 most recent pages tagged news" in PHP, the editor gets a filterable, sortable list widget in the admin: pick a data source, filter by tags or categories, set a limit. The query is resolved at render time. Editorial changes take effect on publish, not on deploy.
A worked example: a news index template
Here is a minimal but realistic template for a news overview page. Save it as config/templates/pages/news_index.xml in your Sulu project:
<?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.0.xsd">
<key>news_index</key>
<view>pages/news_index</view>
<controller>Sulu\Bundle\WebsiteBundle\Controller\DefaultController::indexAction</controller>
<cacheLifetime>2400</cacheLifetime>
<meta>
<title lang="en">News Index</title>
</meta>
<properties>
<property name="title" type="text_line" mandatory="true">
<meta><title lang="en">Title</title></meta>
</property>
<property name="articles" type="smart_content">
<meta><title lang="en">Articles</title></meta>
<params>
<param name="provider" value="pages"/>
<param name="limit" value="10"/>
</params>
</property>
</properties>
</template>The matching Twig view, templates/pages/news_index.html.twig, iterates the resolved collection:
<h1>{{ content.title }}</h1>
<ul>
{% for article in content.articles %}
<li><a href="{{ sulu_content_path(article.url) }}">{{ article.title }}</a></li>
{% endfor %}
</ul>After adding the XML file, clear the cache — template definitions are cached, and forgetting this is the single most common reason a new field "doesn't show up" in the admin:
# Run in the project root, as the web/CLI user (e.g. www-data or your dev user)
php bin/adminconsole cache:clearThen create a page of type "News Index" in the admin UI. The Articles field appears as a Smart Content picker: the editor can restrict the data source to a subtree, filter by the news tag, and cap the result count. Publish a few pages tagged news, reload the frontend, and the list should reflect exactly those filters. If the list is empty, check in order: pages are published (not just saved), the tag matches exactly, and the data source subtree actually contains the pages.
Why this beats the alternatives
Compared to a hand-rolled Symfony controller, you delete an entire category of change requests. Compared to a "related pages" media-style selection, editors don't have to hand-pick items — the list stays current as new tagged pages are published. And because the admin form is generated from the XML, there is no form code to keep in sync with the template.
The division of labor ends up healthy: developers own structure (what fields exist, what types they are, how they render), editors own data (what the filters select). That boundary is exactly where most CMS friction lives, and Smart Content dissolves it.
Trade-offs you should plan for
Template edits are not free refactors. Sulu persists content against the property names in your XML. Rename articles to posts and the existing stored values are orphaned — the admin shows an empty field while the old data still sits in the repository. Treat property renames like database migrations: plan a data migration or accept re-entering content. Before renaming anything on a live site, export or snapshot the content and verify behavior on a staging copy of your exact Sulu version.
Filter options vary. The filters exposed in the Smart Content UI depend on the configured providers and your Sulu version. Don't promise editors "you'll be able to filter by author" until you've confirmed the installed version exposes it. The verification step is cheap: create the template, open the admin, and look at the filter panel.
One template, too many blocks. It's tempting to make one mega-template with every optional block. The admin form becomes a wall of collapsed sections and editors get lost. Splitting into focused templates (a news index, a landing page, a plain article) is almost always cleaner.
Tree-shaped content. Sulu's content lives in a PHPCR repository (via Jackalope, commonly over Doctrine DBAL), so content is a tree. Hierarchical sites fit beautifully; genuinely relational queries ("all events sharing a venue with this one") are less natural than in a relational CMS and may push you toward custom providers.
Try it this week
The fastest way to evaluate this pattern: add the news_index template above to a local Sulu project, clear the admin cache, publish three tagged pages, and confirm the rendered list respects the tag filter and limit. Fifteen minutes of hands-on time tells you more than any feature matrix — and if it fits, you've permanently removed a class of editorial tickets from your backlog.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.