Blog Sections Open
First Steps with Twig in Evolution CMS 1.4.x
A practical introduction to Twig in Evolution CMS 1.4.x, from installation and template wiring to menus, breadcrumbs, and a reusable base layout.
Twig arrived in Evolution CMS long before the current Blade-based stack became standard, and for many 1.4.x projects it was the first clean way to move away from large HTML templates full of chunk calls and inline parser logic. This article republishes and translates an older field guide that showed how to set up Twig on Evolution 1.4.x and gradually convert a real site template.
This is legacy material, but it is still useful when you maintain an older Evolution 1.4.x project or need to understand the migration path toward a cleaner view layer.
What You Need Before You Start
- Evolution CMS 1.4.14 or newer
- PHP 7.2.5 or newer
- The EvoTwig package or the later EvoTwig2 package
- A working understanding of classic Evolution templates, chunks, and DocLister menu calls
The original article pointed readers to the introductory Twig lesson and the official Twig documentation. That advice still holds: treat the package setup guide as the install reference, and use this article as the practical “how do I actually convert a template?” walkthrough.
Installation Notes
The package can be installed like a normal Evolution extra. Composer was optional in the original setup, but if you use it, the package expected the work to happen inside the assets directory.
require: {
"pathologic/evo-twig-lib": "*"
}
After installation, enable the EvoTwig plugin in Elements → Plugins. Twig templates are plain files, usually stored in assets/templates/tpl. A common structure is to keep a base template at the root and partial-like files in a nested folder such as assets/templates/tpl/chunks.
Connecting a Twig Template to an Evolution Template
In the Evolution template code field, point the template to the Twig file:
@FILE:home
The extension is omitted. Evolution resolves the template through the Twig package configuration.
The Three Twig Delimiters
The original tutorial rightly emphasized that Twig uses only three delimiter types:
{% ... %}for logic and control flow{{ ... }}for output{# ... #}for comments
That sounds simple, but once you begin moving from chunks and placeholders to Twig variables, filters, and blocks, the template layer becomes much easier to read and maintain.
Start with a Base Template and Child Templates
A practical Twig setup in Evolution should start with one base layout and several child templates. The base layout keeps the repeated shell of the site: head, header, breadcrumbs, footer, and script includes. Child templates override only the blocks they need.
A minimal child template for a text page looked like this in the original article:
{% extends 'base.tpl' %}
{% block content %}
<div class="section">
{{ resource.content | raw }}
</div>
{% endblock %}
The raw filter matters because Twig escapes output by default. Without it, the stored document HTML would be printed as text instead of rendered markup.
If you need to keep content from the parent block, use parent():
{% block content %}
{{ parent() }}
<div class="section">
{{ resource.content | raw }}
</div>
{% endblock %}
Building the Head Section
One of the first wins in Twig is replacing classic placeholders with readable variables. For example, TV values become resource.someTv, and system configuration values become config.some_setting.
The article also highlighted an important nuance: meta title and description fields should have a fallback, because legacy “default parser value” tricks do not automatically carry over.
<head>
<meta http-equiv="Content-Type" content="text/html; charset=[(modx_charset)]">
<title>{{ resource.titl is empty ? resource.pagetitle : resource.titl }}</title>
<meta name="keywords" content="{{ resource.keyw }}">
<meta name="description" content="{{ resource.desc is empty ? resource.introtext : resource.desc }}">
<base href="{{ config.site_url }}">
<link href="assets/templates/css/vendor.min.css" rel="stylesheet">
<link href="assets/templates/css/style.min.css" rel="stylesheet">
</head>
This same pattern is still useful today: if a field is optional, be explicit about the fallback in the view layer.
Header Data and Utility Filters
Links that would normally be written as classic Evolution tags can be generated directly in Twig. A resource link can be rendered with makeUrl(), and settings can be transformed with Twig filters instead of helper snippets.
{{ makeUrl(7) }}
{{ config.company_phone|replace({'+':'', ' ':'', ')':'', '(':'', '- ':''}) }}
That simple phone cleanup replaced a dedicated helper snippet in the original project.
Rendering Menus: Two Possible Approaches
The article compared two ways to render a menu with DLMenu.
Approach 1: keep DocLister chunk templates and call the snippet from Twig. It works, but it still feels like “classic Evolution inside Twig.”
Approach 2: ask DocLister to return an object and render the structure with a real Twig loop. That is where the code becomes much cleaner.
{% set DL = runSnippet('DLMenu', {
'parents':'0',
'maxDepth':'2',
'returnDLObject':'1'
}) %}
To inspect the structure before rendering it, the article used:
{{ dump(DL.getMenu()) }}
And the final menu loop looked like this:
{% for data in DL.getMenu()[0] %}
<li class="{% if data.children != 0 %}dropdown{% endif %}">
<a href="{{ data.url }}" title="{{ data.title }}"><span>{{ data.title }}</span></a>
{% if data.children %}
<ul>
{% for data in data.children %}
<li class="dropdown"><a href="{{ data.url }}"><span>{{ data.title }}</span></a></li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
This is still one of the clearest examples of how Twig can replace a large family of row and wrapper chunks with a small, readable template loop.
Breadcrumbs and Config-Based Templates
The original write-up also covered a practical limitation: rendering DLCrumbs directly with Twig placeholders was less convenient than expected. The chosen solution was to keep the breadcrumb markup in a configuration file and call the snippet with that configuration.
{{ runSnippet('DLCrumbs', {
'showCurrent':'1',
'config':'crumbs'
})|raw }}
That configuration file should live in your own project-level config location so it does not get overwritten on package updates.
A Final Base Layout Example
By the end of the article, the project had a complete base layout combining Twig variables, DLMenu, DLCrumbs, standard assets, and a content block for child templates.
The important lesson was not only “Twig works in Evolution,” but also this: once a site moves to a proper base template and child-template structure, it becomes much easier to maintain than a network of copied chunks and repeated template fragments.
When to Use This Pattern Today
For older 1.4.x sites, this pattern is still valid. For modern Evolution CMS 3 projects, Blade is now the primary direction, so new builds should usually follow the current stack instead of introducing Twig. Still, if you are maintaining a legacy project that already relies on Twig, the ideas in this article remain practical and worth keeping.
Why Uncached DocLister Calls Return Nothing
A troubleshooting note for cases where a cached DocLister call works but an uncached call with [[!DocLister]] appears to return nothing in Evolution CMS 3.
Migrating Web Users with Extended Rights from Evolution 2.0.4 to 3.x
A practical migration guide for moving web users with extended permissions from Evolution 2.0.4 into Evolution CMS 3.x.