Templates and design: change what a page looks like
This guide is for site builders who want to change how pages look. You will find out which design your site uses, see which template file produced a piece of a page, change it with an override that you can undo, check the syntax, and switch the administration interface to the admin4 design. Follow it top to bottom; each part ends in something you can check. It takes about fifteen minutes.
Every command is run from the installation root, the directory that contains index.php. When you are logged in as root, add --allow-root-user (the examples write it out). You need a running site: Getting started gets you there in minutes.
1. How a template is found
A page is made by .tpl files (the template language is described in part 6). Exponential looks for a template, for example node/view/full.tpl, in this order and uses the first file it finds:
- the override templates whose rules in
override.inimatch the page (a class, a node, a section ...), found indesign/<design>/override/templates/of the site design and of the extensions that carry design files; - the plain
templates/directory of the site design,design/<design>/templates/; - the same directories of the additional designs;
- the
standarddesign,design/standard/templates/, which is the fallback and which you never edit.
A design is a directory with templates/, override/templates/, stylesheets/, images/ and javascript/. It lives in design/ of the installation or in extension/<name>/design/<design>/. An extension that carries a design is switched on with DesignExtensions[]=<name> in its settings/design.ini.append.php.
2. Find out which design you use
./console exp:ini where site/DesignSettings/SiteDesign eng --allow-root-userReplace eng with the siteaccess you are looking at (the folders of settings/siteaccess/). Expected output on the shipped multisite setup:
site.ini/DesignSettings/SiteDesign (load order of siteaccess eng)
1. settings/site.ini scope default
SiteDesign=admin
2. settings/siteaccess/eng/site.ini.append.php scope siteaccess:eng
SiteDesign=media
In effect:
SiteDesign=mediaThe last line is your design. Its files are in design/media/ or in an extension, here extension/sevenx_themes_media/design/media/. To see the other designs of a siteaccess:
./console exp:ini get site/DesignSettings/AdditionalSiteDesignList eng --allow-root-userReference: Extension loading order, Additional extension directories.
3. See which template wrote which markup
Switch on template path comments. They are for development: they add markup to every template, so never leave them on for a production siteaccess or for feeds.
./console exp:ini set site/TemplateSettings/ShowTemplatePathComments enabled siteaccess:eng --allow-root-user
php bin/php/cache.php clear --tag=template --allow-root-userReload a page of the site, open "view source" and search for START. Expected: each piece is wrapped, for example
<!-- START design/media/templates/pagelayout/footer.tpl -->
...
<!-- STOP design/media/templates/pagelayout/footer.tpl -->When an override answered, the original name follows in brackets: <!-- START .../full/article.tpl (node/view/full.tpl) --> reads "the file article.tpl answered the request node/view/full.tpl". Switch it off again with the same command and disabled, and clear the template cache again.
If you prefer a click path, the same switch is in the debug bar: Exp Debug bar, tab Settings, group templates. Details: Template path comments.
4. Make a change with an override
You will restyle one article (one object) without touching anything else. An override has two parts: the template file and a rule saying when it applies.
Click path (no files, undo in one click)
- In the administration interface open Design > Template Editor.
- Open
node/view/full.tplof your design. - Press New override, choose Default copy, enter the Object ID of the article and press Create.
- Edit the new template, add a visible line such as
<p>Override works</p>and save. - Reload the article on the site. The line is there, and with the path comments on you see
START ... (node/view/full.tpl)for it. - To undo, tick the override on the list and press Remove selected: the rule and the file are deleted.
The editor writes settings/siteaccess/<siteaccess>/override.ini.append.php for you, orders overrides by drag and drop and keeps a copy of the file before it writes. See Template editor.
File path (what you keep in version control)
Create the template, in the override directory of your design:
mkdir -p design/media/override/templates/full cp design/standard/templates/node/view/full.tpl design/media/override/templates/full/my_article.tplUse the design name you found in part 2. If your design lives in an extension, create the files there instead.
Add a rule in
settings/siteaccess/eng/override.ini.append.php(or insettings/override/override.ini.append.phpfor every siteaccess):[full_my_article] Source=node/view/full.tpl MatchFile=full/my_article.tpl Subdir=templates Match[object]=1234 */ ?>Sourceis the template being replaced,MatchFileyour file relative tooverride/templates/,Subdiris alwaystemplates,Match[...]the conditions (class_identifier,node,object,section,parent_node,viewmodeand more; all must hold). Rules without aPrioritycome after rules with one; the lowest number is tried first.A new template path needs the override and template caches cleared once:
php bin/php/cache.php clear --id=template-override --allow-root-user php bin/php/cache.php clear --tag=template --allow-root-user php bin/php/cache.php clear --tag=content --allow-root-userExpected: each ends with
PASS.Reload the article; the path comment shows your file.
Later edits to the same file need no clear while the template cache is disabled; with it enabled run the --tag=template line again. Which of several matching overrides wins, and how an extension's overrides are ranked: Template editor and Extension loading order. An extension can also override the templates of a module: Extension module override.
5. Check the syntax before you look at the browser
php bin/php/eztemplatecheck.php -seng design/media/override/templates/full/my_article.tpl --allow-root-userExpected: Template file valid: design/media/override/templates/full/my_article.tpl. A whole design or directory can be given instead of a file (design/). A syntax error is reported with the template and the line; the compiler's warnings on the page name the template and line too.
6. The template language in ten minutes
A template is HTML with { ... } expressions. Comments are {* ... *}.
| You write | Meaning |
|---|---|
{$node.name|wash} | Print a variable. wash escapes HTML; use it on anything a user typed. |
{def $items = fetch( 'content', 'list', hash( 'parent_node_id', $node.node_id, 'limit', 5 ) )} | Declare a variable ({def} once per template; {set $items = ...} changes it later; {undef $items} frees it). fetch reads data, hash builds an array of named values. |
{foreach $items as $child} ... {/foreach} | Loop. {delimiter} and {sequence} help with separators and zebra rows. |
{if $node.depth|gt( 2 )} ... {else} ... {/if} | Condition. Operators are written as pipes: gt, lt, eq, and, or, not. |
{cond( $a, 'yes', 'no' )} | Inline condition. |
<a href={$child.url_alias|ezurl}> | Build a link that respects the siteaccess. ezurl is written without quotes in an attribute. |
<img src={'logo.png'|ezimage}> | Find an image in the design's images/. |
{'Search'|i18n( 'design/media/pagelayout' )} | A translated string. |
{attribute_view_gui attribute=$node.data_map.title} | Show a content attribute with its datatype template. |
{node_view_gui content_node=$child view='line'} | Show a node with its node/view/line.tpl, and the override for it. |
{include uri='design:parts/header.tpl' title=$node.name} | Include another template; design: names a path inside the design. |
A complete, small template to try in the override from part 4 (it lists the children of the article's parent, ten at most):
<h1>$node.name|wash</h1>
def $siblings = fetch( 'content', 'list', hash( 'parent_node_id', $node.parent_node_id, 'limit', 10 ) )
<ul>
foreach $siblings as $sibling
<li><a href=$sibling.url_alias|ezurl>$sibling.name|wash</a></li>
/foreach
</ul>
undef $siblingsExpected: the heading followed by a list of links. Reference lists of every operator: String template operators, Role and policy template operators.
7. Debugging templates
When a page is wrong, work down this list:
| Question | Do this |
|---|---|
| Which file wrote it? | Template path comments (part 3). |
| Why this file and not mine? | Check the rule: Source, MatchFile and Match[...], then clear template-override and template. ./console exp:ini where override/<block>/MatchFile eng --allow-root-user lists the files that define your block. |
| Is the syntax right? | eztemplatecheck.php (part 5). |
| Which templates, how long, how many queries? | The Exp Debug bar: tabs Templates, Timing, SQL. Debug output itself is switched on for yourself with [DebugSettings] DebugOutput=enabled and DebugByUser/DebugByIP; see Debug output improvements. |
| The page does not change after an edit | Clear by what you changed: Operating a site, part 2. A changed stylesheet also needs the template-block cache cleared; see that guide. |
| Stale pages for a while | Content view cache: php bin/php/cache.php clear --tag=content --allow-root-user. |
An operator parameter without a default value
A template operator lists its parameters with a default value for each one a template may leave out. When an
operator (often one of your own extension) gives no default for a parameter and a template leaves it out, the
operator gets null for it and the debug output says which operator and which parameter:
Warning: eZTemplate:eZTemplateOperatorElement @ design/site/templates/full/article.tpl:12[4]
Operator 'my_operator' has no default value for parameter 'length'The warning is written once per operator and parameter in a request, however often the operator runs. A compiled
template ([TemplateSettings] TemplateCompile=enabled, the default) is warned once, when it is compiled, with
(compiling <template>) after the text; running the compiled template does not check again, so it costs nothing.
To see it again, clear the template cache. Fix it in the operator's namedParameterList(): give the parameter a
'default' => .... Nothing changes on an upgrade except the new warning, with thanks to Mugo Web, whose LoveStack
commit 0bb0bac618 brought the idea.
Edit links in the list of used templates
With [TemplateSettings] ShowUsedTemplates=enabled the debug output lists the templates of the page, each with an
"Edit" icon that opens the template in Setup > Templates (visual/templateedit) of the current siteaccess. On a
public siteaccess that editor is usually not where you want to edit; [DebugSettings] UsedTemplateEditUrl sets the
link, as a sprintf() format:
| Placeholder | Is |
|---|---|
%1$s | the address of visual/templateedit in the current siteaccess followed by the template file |
%2$s | the current siteaccess |
%3$s | the template file, for example design/standard/templates/node/view/full.tpl |
# settings/override/site.ini.append.php
[DebugSettings]
# empty (the default): %1$s/(siteAccess)/%2$s, the link the list always had
# open the template in the administration interface's editor instead:
UsedTemplateEditUrl=/admin/visual/templateedit/%3$s/(siteAccess)/%2$sExpected: reload a page with debug output on; the "Edit" icons of the list point at
/admin/visual/templateedit/design/.../template.tpl/(siteAccess)/<siteaccess>. A format sprintf() cannot use
(a %4$s, a stray %) gives the usual link and a warning. Upgrading changes nothing: the setting is empty.
This follows LoveStack commit fb20bee0d2 by Mugo Web, with thanks.
8. Switch the administration interface to admin4
admin4 is a complete admin design with a light and a dark mode that does not need admin3, admin2 or admin to be present. Set it for the admin siteaccess only:
./console exp:ini set site/DesignSettings/SiteDesign admin4 siteaccess:admin --allow-root-user
php bin/php/cache.php clear --all --allow-root-userExpected: both end with PASS; reload the administration interface and the header carries a light/dark toggle after the search box. To go back, set SiteDesign to admin3 and clear again. To try it first without changing anything, open the siteaccess admintest_admin4. Everything about it, including how to write your own styles on top (design/admin4/stylesheets/admin4.css, no cascade layers): The admin4 design; the earlier responsive design: admin3.
9. Site data in templates
A template can read a row of ezsite_data (the name/value pairs listed under Setup > Site data) once its name is
allowed in site.ini:
# settings/override/site.ini.append.php
[SiteDataSettings]
TemplateReadable[]=my-flagdef $flag = fetch( 'content', 'site_data', hash( 'name', 'my-flag' ) )
if $flag$flag.value|wash/if
undef $flagThe fetch gives the row (.name, .value) or false for a name that is not listed or not stored; names are whole
names, not patterns. It only reads: no template can write a row. The list is empty in every installation, new or
upgraded, so nothing is readable until you add a name; asking for one not listed leaves a notice in the debug
output. PHP code keeps using eZSiteData::fetchByName(), and eZSiteData::createNew( $name, $value ) stores a row
in one call. This comes from LoveStack (mugoweb/ezpublish-legacy, commits 55029bd473 and a679a13176), with thanks
to Mugo Web; Exponential adds the allow-list.
10. Encoding a value for a URL: rawurlencode and rawurldecode
urlencode turns a space into +, as a form does. For a part of a path, or a value that another system reads
with RFC 3986 rules, use rawurlencode, which gives %20; rawurldecode is its reverse:
<a href=concat( '/search/', $term|rawurlencode )|ezurl>$term|wash</a>
'x%20y%2Bz'|rawurldecode {* x y+z *}Both are listed in settings/template.ini [PHP] PHPOperatorList[], in every installation, new or upgraded;
urlencode and urldecode are unchanged. The list is read-only from the settings page and from code, as it
names PHP functions. With thanks to Mugo Web (LoveStack, commit 627004e5c4).
11. Telling a layout view apart: the layout key
A page shown through layout/set/<layout>/... (for example a print view) is rendered with the override key
layout set to that layout, which an override can match:
# settings/siteaccess/<site>/override.ini.append.php
[full_article_print]
Source=node/view/full.tpl
MatchFile=full/article_print.tpl
Subdir=templates
Match[class_identifier]=article
Match[layout]=printSince 6.0.15 every node view has the key, as an empty value when no layout is used, so a node template can always ask for it without a check whether it exists:
if $DesignKeys:used.layout|eq( 'print' ).../ifOverrides that do not name layout match as before. In PHP, eZTemplateDesignResource::instance()->setKeyValue( $key, $value, $override = true ) sets one key; with $override = false a key that is already set keeps its
value, which is how the node view sets layout to '' without replacing the layout of layout/set. With
thanks to Mugo Web (LoveStack, commit 856a95e240).
12. Response headers of error pages
The error pages (a page that does not exist, a refused access) send the status line of
settings/error.ini [HTTPError-<code>] HTTPName. Further headers can be added per code, for example to let a
proxy keep the "not found" page for five minutes:
# settings/override/error.ini.append.php
[HTTPError-404]
HeaderList[]
HeaderList[Cache-Control]=public, must-revalidate, max-age=300The headers follow the status lines and are stored with the page in the content view cache, so a cached error
page sends them again. A name that is not a valid header name, or a value with a line break, is left out with a
warning in the debug output. HeaderList[Status]=<text> stands for HTTPName when that is not set. The list is
empty in every installation, new or upgraded, so nothing changes until you add an entry. With thanks to Mugo Web
(LoveStack, commit 97690e9c93).
Related pages
- The content model and editing content: the classes and attributes your templates show.
- Operating a site: caches, cronjobs and repairs.
- Deploying: move your design to a server.
- Feature pages: Template editor, Template path comments, Icon themes in extensions, Hidden admin tabs.
- Upgrade notes: Debug bar, Pagination settings.
- Extensions: carry your design in an extension of its own.
Become an Exponential Partner
Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster.