Contao Twig Template Architecture: Boundaries, Overrides, and Operational Checks
When Contao projects grow, mixing PHP and HTML in templates creates maintenance debt. This architecture note explains how Twig enforces boundaries, the smallest safe override, and how to verify cache and debug behavior.
28 Aug 2025, 10:32 UTC

The problem: PHP leaking into presentation
When a Contao site starts as a handful of pages, it is tempting to sprinkle PHP directly into template files to get data exactly where you want it. Six months later, the same templates contain database queries, conditional business rules, and HTML, and no one can safely change the markup without breaking the logic. The useful decision is to treat Twig templates as a presentation-only layer: controllers and classes prepare data, templates render it, and the boundary between them stays explicit.
Requirements for a maintainable template layer
This note assumes a modern Contao installation (4.13 or 5.x) where Twig is available as the templating engine. Older versions or projects still using the legacy PHP template system will require migration before the boundary described here is real. Before making changes, confirm three things:
- Twig is the active engine. Check the system settings or configuration to ensure templates are resolved through Twig rather than legacy PHP files.
- The theme directory is writable by the user or process that deploys code.
- The cache directory is writable by the web server user so compiled Twig templates can be stored and reused.
The smallest suitable design: one Twig override
The smallest change that respects the architecture is a single custom Twig file inside your theme's templates/ directory. It should read only the context variables passed by the Contao controller and avoid any direct database or model access.
Where the file lives
Place the override under templates/<theme>/ using the naming convention of the element or module you are replacing. For example, a custom content element might use a path like templates/<theme>/content_element/<element>.html.twig. Contao resolves this before the default system template.
What the override contains
Keep the file focused on markup. If the base layout defines blocks, extend it and override only the block you need. The example below renders two context variables, headline and text, which the element controller is expected to provide:
{# templates/<theme>/content_element/custom_promo.html.twig #}
{% block content %}
<div class="promo">
<h2>{{ headline|escape }}</h2>
<p>{{ text|raw }}</p>
</div>
{% endblock %}Use |escape for untrusted strings. Use |raw only when the source is trusted and HTML output is intentional.
Trust and data boundaries
In Contao's Twig integration, the controller assembles a context array and hands it to the template. The template can read those values, but it should not instantiate models, run SQL, or call services that mutate state. This keeps the data contract testable and the template replaceable.
Custom Twig functions and filters are the main way this boundary erodes. A filter that formats a date is presentation logic. A function that fetches related records from the database is business logic wearing a template costume. If you need the latter, move it into a PHP class and pass the result as a context variable.
PHP template vs. Twig template
| Aspect | Legacy PHP template | Twig template |
|---|---|---|
| Logic allowed | Full PHP execution | Restricted functions and filters |
| Data access | Direct model calls common | Context variables only |
| Compilation | Interpreted per request | Compiled to PHP and cached |
| Error visibility | Often silent or partial HTML | Exception or HTTP 500 unless debug mode is on |
Operational checks
Clearing the template cache
Twig templates are compiled to PHP and stored in the cache. After adding or changing an override, clear the cache so the new file is picked up. Run the following from the project root:
cd <project-root>
php bin/console cache:clearPermissions and risks: The command must run as a user allowed to delete cache files. After clearing, the web server user needs write access to the cache directory (commonly var/cache in modern Contao installations) to regenerate compiled templates. Running the command as the wrong user can leave files with incorrect ownership, which later causes blank pages.
Expected check: The command exits without permission errors, and the frontend page loads normally.
Inspecting context variables in debug mode
To see exactly which variables a template receives, enable debug mode. Edit the .env file at the project root and set the environment to development:
APP_ENV=devAlternatively, some setups use APP_DEBUG=1; verify which variable your Contao version respects. Reload the frontend page. The debug toolbar or profiler should list the active template and its context variables.
Risk: Debug mode exposes internal paths and configuration. Disable it immediately after inspection and never leave it enabled in production.
Failure modes and what they look like
- Template not found: Twig cannot locate your override. Check the exact filename, theme registration, and directory case.
- Syntax error: An unclosed tag or invalid filter causes an exception. In production this typically returns HTTP 500; in debug mode you get a stack trace.
- Stale cache: The override exists but the old compiled template is still served. Clear the cache and confirm the file timestamp in the cache directory changes.
- Cache permission failure: The web server cannot write compiled templates, resulting in a blank page or 500 error. Verify ownership and permissions on the cache directory.
When this design stops fitting
The Twig boundary is not always the right final state. Reconsider the design if:
- Legacy PHP templates dominate the project. A piecemeal Twig override adds cognitive overhead without fixing the underlying split. Migrate the feature first, then enforce the boundary.
- Deep inheritance chains appear. Nesting layouts three or four levels deep increases memory usage and makes it hard to trace where a variable or block originates. Flatten the hierarchy.
- Templates accumulate custom data-fetching extensions. That signals the presentation layer has become a service locator. Push the logic back into controllers or domain classes.
- Multiple themes share overrides. Ensure overrides are theme-scoped so one theme's markup does not silently affect another.
Rollback and verification
Creating a template file changes deployed state. To roll back, delete the override file from the theme's templates/ directory and run php bin/console cache:clear again. Contao will fall back to the default template.
To verify the result, load the affected frontend page in a browser and inspect the rendered HTML. Confirm the expected markup appears and no error page is shown. Check the application logs under var/log/ for Twig exceptions if the page behaves unexpectedly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.