Skip to the content
Documentation
Help Start
Reading in:
  • German: 9,075 of 9,075 pages
About the help in other languages
All help pages

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…

Download as PDF

Address: https://doc.exponential.earth/exphelp/viewer/guides/templates-and-design.md

On this page

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:

  1. the override templates whose rules in override.ini match the page (a class, a node, a section ...), found in design/<design>/override/templates/ of the site design and of the extensions that carry design files;
  2. the plain templates/ directory of the site design, design/<design>/templates/;
  3. the same directories of the additional designs;
  4. the standard design, 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-user

Replace 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=media

The 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-user

Reference: 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-user

Reload 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)

  1. In the administration interface open Design > Template Editor.
  2. Open node/view/full.tpl of your design.
  3. Press New override, choose Default copy, enter the Object ID of the article and press Create.
  4. Edit the new template, add a visible line such as <p>Override works</p> and save.
  5. 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.
  6. 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)

  1. 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.tpl

    Use the design name you found in part 2. If your design lives in an extension, create the files there instead.

  2. Add a rule in settings/siteaccess/eng/override.ini.append.php (or in settings/override/override.ini.append.php for every siteaccess):

    <?php /* #?ini charset="utf-8"?
    
    [full_my_article]
    Source=node/view/full.tpl
    MatchFile=full/my_article.tpl
    Subdir=templates
    Match[object]=1234
    
    */ ?>

    Source is the template being replaced, MatchFile your file relative to override/templates/, Subdir is always templates, Match[...] the conditions (class_identifier, node, object, section, parent_node, viewmode and more; all must hold). Rules without a Priority come after rules with one; the lowest number is tried first.

  3. 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-user

    Expected: each ends with PASS.

  4. 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-user

Expected: 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 writeMeaning
{$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 $siblings}

Expected: 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:

QuestionDo 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 editClear 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 whileContent 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.

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:

PlaceholderIs
%1$sthe address of visual/templateedit in the current siteaccess followed by the template file
%2$sthe current siteaccess
%3$sthe 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$s

Expected: 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-user

Expected: 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-flag
{def $flag = fetch( 'content', 'site_data', hash( 'name', 'my-flag' ) )}
{if $flag}{$flag.value|wash}{/if}
{undef $flag}

The 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]=print

Since 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' )}...{/if}

Overrides 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=300

The 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).

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.

Something wrong on this page? Report an error

Documentation license

This documentation is free documentation, licensed under the GNU Free Documentation License 1.3 or later.

Exponential Copyright © 1998-2026 Exponential Foundation and 7x