Joomla Template Overrides and Alternative Layouts
|
This content was generated with the assistance of AI and should be verified against the official Joomla documentation before being relied on in production. This sub-section’s Joomla bibliography lists the reference material consulted while preparing these pages. |
A template override is a file inside a template’s html folder that replaces the markup an extension would otherwise
print. It is the supported way to change the output of articles, menus, modules, plugins and small shared layouts
without editing core files, which the next update would overwrite. An alternative layout is a second version of that
markup that is only used where an editor or site builder selects it. This page was checked on 2026-10-30 against
Joomla 6.2.x and PHP 8.3 or later. It explains the lookup rules, the Templates manager screens, the checker that
flags overrides whose original changed during an update, the custom-fields layout, and the habits that keep a set of
overrides cheap to maintain.
The people involved differ from step to step. A themer writes the override files in a template such as
cassiopeia_tallpine (the child template of Cassiopeia used by the Tallpine Hiking Club examples). A site builder
selects an alternative layout for a module, a category or a menu item. An administrator reviews the overrides after
every core or extension update. An extension developer decides which parts of a component or module are exposed as
layouts so that they can be overridden at all. Child templates themselves are explained in
Child templates, template styles in
Templates and template styles, and the module chromes and layout
files used by <jdoc:include> in Layouts and module chromes.
What can be overridden
Everything that ends up in the page through a PHP template file can be overridden if the extension loads it in the
standard way. There are four families, each with its own folder name in the template’s html folder.
| Family | Core location of the original | Override location |
|---|---|---|
Component view |
|
|
Module |
|
|
Plugin |
|
|
Layout (shared fragments) |
|
|
A few conventions are worth remembering:
-
The folder under
htmlis the extension’s technical name:com_,mod_orplg_<group>_<name>. For a plugin the folder name joins the group and the plugin name, soplugins/content/pagenavigationbecomeshtml/plg_content_pagenavigation. -
Since Joomla 4.0 the view files of the core components live directly in
components/com_x/tmpl/<view>/. Older material, and many third-party components, still useviews/<view>/tmpl/. The override location is the same in both schemes:html/com_x/<view>/<file>.php. The Templates manager copes with both layouts of the component. -
An override replaces the file with the same name. Only files that exist in the override folder replace the original; every other file of the view (for example
default_links.phpof the article category list) is still loaded from the component. Copy only what you change. -
Only the template part of an extension is overridden. Controllers, models, helpers and the Dispatcher of a module are not, so an override cannot change which data is loaded, only how it is printed. If you need different data, the tools are a plugin event, a module parameter or a custom view (see Component site views and routing).
-
Administrator overrides work the same way for an administrator template such as Atum: the Templates manager has a separate list of administrator templates, and the files go to
administrator/templates/atum/html/. See The Atum administrator template. -
Not every extension supports overrides. An extension that echoes markup from a helper class, or from a plugin without a
tmplfolder, offers nothing to override. The Templates manager only lists what it finds.
How Joomla finds the file
The lookup differs slightly for views, modules, plugins and layouts, but the idea is always the same: look in the active template first, then in its parent, then in the extension. Because Tallpine uses a child template, the order includes two template folders. The figure summarises the four families; the sections below explain each branch with the source that implements it.
Component views
A component view (a class that extends HtmlView) keeps a list of template search paths. The view adds the
component’s own tmpl folder, and its _setPath() method adds the template folders. When the active template has a
parent, the parent’s html/com_x/view folder is added first and the child’s folder after it; because paths are
prepended, the child is searched first, then the parent, then the component. The view then asks for
<layout>.php, where the layout is default unless the menu item or the article selected another one (see
alternative layouts), and every sub-template loaded with $this->loadTemplate('links') is
looked up as default_links.php in the same folders.
Two consequences follow. First, a child template can override a view that the parent already overrides, and the child
wins. Second, an override in the parent still applies when the child has none, so a site that uses a Cassiopeia child
keeps the overrides Cassiopeia itself ships in html/layouts, html/mod_custom, html/mod_menu and html/tinymce.
Modules
ModuleHelper::getLayoutPath($module, $layout) builds three candidates from the module name and the selected layout:
the active template, the parent template and the module’s own tmpl folder. The first existing file wins and
modules/<module>/tmpl/default.php is the last resort. The layout value stored in the module is either _:default
(the module’s own file) or template:layout (a file from a template’s html/mod_x folder). An underscore as the
template part means "the current template". This is also how a module can load a layout of a different template than
the page uses, and why a layout that depends on one template’s CSS can break when the module is shown on pages that use
another template style.
Plugins
PluginHelper::getLayoutPath($type, $name, $layout) checks html/plg_<type>_<name>/<layout>.php in the active
template, then in the parent, then plugins/<type>/<name>/tmpl/<layout>.php and finally the plugin’s default.php. The
plugin must call this helper (the Extension class of plg_content_pagenavigation does) for the override to apply. A
plugin has no alternative layout selector in the administrator; an override always applies.
Layouts
Small shared fragments are rendered with LayoutHelper::render('joomla.content.info_block', $data). The dotted ID is
turned into a path (joomla/content/info_block.php) and FileLayout searches a list of include paths, in this order:
-
a base path passed by the caller (module and plugin layouts use this to point at their own folder)
-
html/layouts/<component>in the active template, then in the parent, when acomponentoption is given -
the component’s own
layoutsfolder, incomponents/<component>/layouts -
html/layoutsin the active template, then in the parent -
layoutsin the Joomla root
The component option is what lets FieldsHelper::render() find html/layouts/com_content/field/render.php before
html/layouts/com_fields/field/render.php. The Joomla Manual documents the same order for components and adds render
options that are useful when debugging: debug prints every searched path and the file that was chosen, and suffixes
makes the search look for div.abc.php before div.php. Sub-layouts, loaded with $this->sublayout('sub', $data),
come from a folder named after the parent layout and inherit the same paths.
Source: Joomla Manual: Layouts
Layout paths are cached per request, keyed by the layout ID and the list of paths. A file added during a request is not seen by a layout that was already looked up, which only matters in tests.
Creating overrides in the Templates manager
The manager copies the files for you, so the structure is right the first time.
-
Open System → Site Templates and click the Details and Files link of the template (for Tallpine, the
cassiopeia_tallpineentry). The Templates: Customise screen has the tabs Editor, Create Overrides, Updated Files, Description and the file tree. -
Open Create Overrides. The panels Modules, Components, Plugins and Layouts list every extension on the site that has something to override. Click an entry, for example Components → com_content → article.
-
Joomla copies the PHP files of that view into the template’s
htmlfolder, shows a message such as "Override created in /templates/cassiopeia_tallpine/html/com_content/article" and returns to the Editor tab. -
Delete the files you do not need. Select the file in the tree and use Delete File, or Manage Folders for a whole folder. Delete only what you created yourself.
-
Edit the file in the code editor. Show Original File displays the untouched core file and Show Differences marks added lines in green and removed lines in red, inline or side by side, depending on the File Diff Display option.
The folder mapping used by the copy step is simple. A module goes to html/<module>; a component view to
html/<component>/<view>; a component’s own layouts folder to html/layouts/<component>/<layout>; a plugin to
html/plg_<group>_<plugin>; the shared layouts to html/layouts/<group>/<layout>. If the target file already exists,
the manager keeps both: the new copy is written with a timestamp in the name, for example
default-20261030-101500.php. Those stray files are not loaded (the lookup wants default.php), but they clutter the
template, so remove them.
A Cassiopeia site ships overrides for layouts (the module chromes), mod_custom, mod_menu and tinymce, but none
for com_content. The Tallpine child template therefore starts with an almost empty html folder, and each override is
a deliberate decision.
Creating overrides without the manager
The manager is only a convenience; an override is a file in a folder. In the Docker development setup described in Local development with Docker the template is a bind mount, so a plain file copy works and also gives the override a place in version control:
# run from the Joomla root; the template folder is a bind mount of the project's templates/cassiopeia_tallpine
T=templates/cassiopeia_tallpine/html
mkdir -p "$T/com_content/article" "$T/mod_tallpine_nexthikes" "$T/layouts/joomla/content"
cp components/com_content/tmpl/article/default.php "$T/com_content/article/default.php"
cp modules/mod_tallpine_nexthikes/tmpl/default.php "$T/mod_tallpine_nexthikes/default.php"
cp layouts/joomla/content/info_block.php "$T/layouts/joomla/content/info_block.php"
git add "$T" && git status --short "$T"
The result of the copy is the html tree the rest of this page builds up:
templates/cassiopeia_tallpine/
templateDetails.xml
joomla.asset.json
language/en-GB/tpl_cassiopeia_tallpine.sys.ini
html/
com_content/
article/
default.php override of the article view
trailreport.php alternative layout (no XML file)
trailmap.php menu item layout ...
trailmap.xml ... which needs this XML file
mod_tallpine_nexthikes/
default.php override of the module
compact.php alternative module layout
plg_content_pagenavigation/
default.php override of a plugin template
layouts/
joomla/content/info_block.php
com_fields/field/render.php
Overriding a component view: the Tallpine trail article
The article view is the most common override. The core file prints the title, the info block, the images, the text, the
tags and the plugin events. Anything the override leaves out disappears from the page, so the safest approach is to copy
the core file and change a few lines. The example below keeps every event output of the core file and adds a trail facts
block fed by the custom fields of the article (distance-km, elevation-gain, difficulty, gpx-file). It is
simplified: the core file also prints icons, images and the pagination, which a real copy keeps.
<?php
// templates/cassiopeia_tallpine/html/com_content/article/default.php
// Based on components/com_content/tmpl/article/default.php of Joomla 6.2. Re-check the diff after every core update.
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\Component\Fields\Administrator\Helper\FieldsHelper;
/** @var \Joomla\Component\Content\Site\View\Article\HtmlView $this */
$params = $this->item->params;
$wa = $this->getDocument()->getWebAssetManager();
// The template's own CSS, declared in joomla.asset.json
$wa->useStyle('template.tallpine.trail');
// Look the custom fields up by name; jcfields is keyed by field ID
$fields = [];
foreach ($this->item->jcfields ?? [] as $field) {
$fields[$field->name] = $field;
}
$showFacts = isset($fields['distance-km']) || isset($fields['elevation-gain']);
?>
<div class="com-content-article item-page" itemscope itemtype="https://schema.org/Article">
<div class="page-header">
<h1 itemprop="headline"><?php echo $this->escape($this->item->title); ?></h1>
</div>
<?php echo $this->item->event->afterDisplayTitle; ?>
<?php if ($showFacts) : ?>
<section class="tallpine-trail-facts" aria-label="<?php echo $this->escape(Text::_('TPL_CASSIOPEIA_TALLPINE_TRAIL_FACTS')); ?>">
<dl>
<?php foreach (['distance-km', 'elevation-gain', 'difficulty'] as $name) : ?>
<?php if (isset($fields[$name]) && $fields[$name]->value !== '') : ?>
<dt><?php echo $this->escape(Text::_($fields[$name]->label)); ?></dt>
<dd><?php echo FieldsHelper::render($fields[$name]->context, 'field.render', ['field' => $fields[$name]]); ?></dd>
<?php endif; ?>
<?php endforeach; ?>
</dl>
</section>
<?php endif; ?>
<?php echo LayoutHelper::render('joomla.content.info_block', ['item' => $this->item, 'params' => $params, 'position' => 'above']); ?>
<?php echo $this->item->event->beforeDisplayContent; ?>
<div itemprop="articleBody" class="com-content-article__body">
<?php echo $this->item->text; ?>
</div>
<?php if (isset($fields['gpx-file']) && $fields['gpx-file']->value !== '') : ?>
<p class="tallpine-gpx">
<?php echo FieldsHelper::render($fields['gpx-file']->context, 'field.render', ['field' => $fields['gpx-file']]); ?>
</p>
<?php endif; ?>
<?php echo LayoutHelper::render('joomla.content.info_block', ['item' => $this->item, 'params' => $params, 'position' => 'below']); ?>
<?php echo $this->item->event->afterDisplayContent; ?>
</div>
Points to check in an override of this kind:
-
Keep the event outputs.
afterDisplayTitle,beforeDisplayContentandafterDisplayContentare where content plugins add their output: custom fields with Automatic Display, vote, page navigation, schema.org. If an override drops one of them, the plugin appears to be broken. The three variables are the ones the User Guide names for the contact view as well. -
Escape what you print.
$this->escape()is the view’s escaping method. Values that come from the type plugin ($field->value) are already prepared for output and must not be escaped twice, whilerawvalueis the stored text and must be escaped by the override. Never print request data without escaping it. -
Use the Web Asset Manager. Request CSS and JavaScript with
useStyle()anduseScript()and register the assets in the template’sjoomla.asset.json; do not write<link>or<script>tags in the override. See The Web Asset Manager. -
Use namespaced classes. Overrides written for Joomla 3 call
JText,JHtml,JRoute,JFactoryorJLayoutHelper. ThoseJ*aliases are not loaded by default on a new 6.x site; they exist only in the class map of the Behaviour - Backward Compatibility 6 plugin, which is off by default on new installs. Replace them withJoomla\CMS\Language\Text,Joomla\CMS\HTML\HTMLHelper,Joomla\CMS\Router\Route,Joomla\CMS\FactoryandJoomla\CMS\Layout\LayoutHelper. Likewise, guard the file withdefined('_JEXEC') or die;, because Joomla 6 no longer definesJPATH_PLATFORMunless the backward-compatibility plugin is on. In Joomla 6 the administrator views also lose$this->db, so an override that queries the database through the view should use a model or a service instead (see Upgrading from Joomla 5 to 6). -
Respect accessibility. The headings, landmarks, labels and contrast of the original are part of its quality. The rules of the web platform apply unchanged; see Accessibility and Colour modes, RTL and accessibility.
Overriding a module: mod_tallpine_nexthikes
A module override replaces tmpl/default.php of the module (or any other layout file the module offers). A well-written
module renders only from variables prepared by its Dispatcher and Helper, so the override can change the markup without
touching the data. The next hikes module of the Tallpine package passes $list (an array of objects with title,
link and start) to its layout.
<?php
// templates/cassiopeia_tallpine/html/mod_tallpine_nexthikes/default.php
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
/**
* @var \Joomla\Registry\Registry $params
* @var \stdClass $module
* @var \stdClass[] $list
*/
if (empty($list)) {
return;
}
?>
<ul class="mod-tallpine-nexthikes list-unstyled">
<?php foreach ($list as $hike) : ?>
<li class="mod-tallpine-nexthikes__item">
<a href="<?php echo htmlspecialchars($hike->link, ENT_QUOTES, 'UTF-8'); ?>">
<?php echo htmlspecialchars($hike->title, ENT_QUOTES, 'UTF-8'); ?>
</a>
<time datetime="<?php echo htmlspecialchars($hike->start, ENT_QUOTES, 'UTF-8'); ?>">
<?php echo Text::_('TPL_CASSIOPEIA_TALLPINE_STARTS'); ?>
<?php echo htmlspecialchars($hike->start, ENT_QUOTES, 'UTF-8'); ?>
</time>
</li>
<?php endforeach; ?>
</ul>
The variables available in a module layout are those documented for modules in
Module development. The module wrapper (the chrome selected under Advanced →
Module Style) is not part of the module override; it comes from html/layouts/chromes, as explained in
Layouts and module chromes.
The Developer Manual adds a warning: a module’s own layouts folder (and a plugin’s) is not an override location and is
not searched automatically. Layouts rendered by a module or plugin are found only because the caller passes the folder
as the base path of LayoutHelper::render().
Overriding a plugin template
Few core plugins print anything, and those that do use a tmpl folder. plg_content_pagenavigation prints the
previous/next navigation of an article. The Tallpine template wants the links to name the destination (previous and
next trail) instead of generic labels.
<?php
// templates/cassiopeia_tallpine/html/plg_content_pagenavigation/default.php
// Based on plugins/content/pagenavigation/tmpl/default.php, with the trail titles added.
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
/**
* @var \Joomla\Plugin\Content\PageNavigation\Extension\PageNavigation $this
* @var \stdClass $row
* @var \stdClass[] $rows
* @var int $location
*/
$this->loadLanguage();
?>
<nav class="pagenavigation tallpine-trail-nav" aria-label="<?php echo Text::_('PLG_PAGENAVIGATION_ARIA_LABEL'); ?>">
<ul class="pagination ms-0">
<?php if ($row->prev) : ?>
<li class="page-item previous">
<a class="page-link" href="<?php echo Route::_($row->prev); ?>" rel="prev">
<span class="visually-hidden"><?php echo Text::_('JPREVIOUS'); ?>:</span>
<?php echo htmlspecialchars($rows[$location - 1]->title, ENT_QUOTES, 'UTF-8'); ?>
</a>
</li>
<?php endif; ?>
<?php if ($row->next) : ?>
<li class="page-item next">
<a class="page-link" href="<?php echo Route::_($row->next); ?>" rel="next">
<span class="visually-hidden"><?php echo Text::_('JNEXT'); ?>:</span>
<?php echo htmlspecialchars($rows[$location + 1]->title, ENT_QUOTES, 'UTF-8'); ?>
</a>
</li>
<?php endif; ?>
</ul>
</nav>
Plugin overrides show how fragile copies can be. The template file reads variables ($row, $rows, $location) that
the plugin’s Extension class sets before it includes the file, and it calls $this->loadLanguage(). When the plugin’s
author renames a variable, the override silently prints nothing. That is exactly the kind of change the checker
described below is designed to point out. Core plugin classes are final in Joomla 6, so the template file is the only
supported extension point for the markup.
Overriding a layout
Layouts are the small fragments that several views share: the info block (author, category, dates, hits), the read-more button, the intro and full images, the icons, the pagination, the form-field markup and the module chromes. One override changes every place that uses the fragment.
The article info block lists the author, category, publication date and hit counter. The Tallpine board decided that trail reports should show the category and the date but not the author name or the hit counter. Instead of editing the view, the template overrides the shared layout. Because the same layout is used by blog and list views, the override applies there as well.
<?php
// templates/cassiopeia_tallpine/html/layouts/joomla/content/info_block.php
// Based on layouts/joomla/content/info_block.php
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
$item = $displayData['item'];
$params = $displayData['params'];
$position = $displayData['position'] ?? 'above';
?>
<dl class="article-info text-muted">
<dt class="article-info-term visually-hidden"><?php echo Text::_('COM_CONTENT_ARTICLE_INFO'); ?></dt>
<?php if ($params->get('show_category')) : ?>
<dd class="category-name">
<?php echo $this->sublayout('category', $displayData); ?>
</dd>
<?php endif; ?>
<?php if ($params->get('show_publish_date')) : ?>
<dd class="published">
<?php echo $this->sublayout('publish_date', $displayData); ?>
</dd>
<?php endif; ?>
</dl>
Source: Joomla Manual: Layouts
The layout ID is translated to a path by replacing dots with slashes: joomla.content.info_block becomes
layouts/joomla/content/info_block.php, and its override html/layouts/joomla/content/info_block.php. The sub-layout
calls load info_block/category.php and the other parts from the folder named after the parent; they are looked up with
the same search order, so each part can also be overridden on its own. Copy those files only when you need to change
them.
The three most useful layout families for a themer are:
-
html/layouts/joomla/...for the shared fragments (content, form, edit, toolbar, system messages, pagination) -
html/layouts/chromes/...for module chromes (the Cassiopeia chromes live here) -
html/layouts/<component>/...for layouts that a component renders itself, for examplehtml/layouts/com_fields/field/render.php
Debugging a layout lookup
When an override does not apply, ask Joomla where it looked. Passing the debug option prints the include paths and the
file that was chosen:
<?php
use Joomla\CMS\Layout\LayoutHelper;
echo LayoutHelper::render(
'joomla.content.info_block',
['item' => $this->item, 'params' => $this->item->params, 'position' => 'above'],
null,
['debug' => true, 'component' => 'com_content']
);
Source: Joomla Manual: Layouts
Remove the option before deploying. For views, modules and plugins the checks are simpler: confirm the folder name, the file name and its capitalisation (file names are case sensitive on Linux), then confirm that the template style that renders the page is the one you edited (see Templates and template styles) and clear the cache if page or view caching is on (see Caching and performance).
Alternative layouts
A template override replaces the default layout for everyone. An alternative layout is a second file in the same
folder that applies only where it is selected, which lets one template keep several versions of the same view. The
files live in html/<extension folder>/ just like overrides, but they have a different name. Their rules:
-
The base file name has no underscore.
compact.phpandtrailreport.phpare alternative layouts;default.phpis the override of the default layout. The module layout selector lists only files whose name contains no underscore, becausename_part.phpis how a layout loads its own sub-templates (default_links.php). -
Companion files use the same base name plus an underscore part (
trailreport_gallery.php). -
The layout is listed in the selector grouped by origin: From Module or From Component for the extension’s own layouts, and From <Template name> Template for the layouts of each enabled template. A child template that adds layouts shows its own group.
-
Plugins do not offer an alternative layout selector. Only an override applies.
The selector shows the file name unless a language key provides a label. The Joomla User Guide names the keys; for
modules the selector builds TPL_<TEMPLATE>_<MODULE>_LAYOUT_<NAME> in upper case from the template element, the module
and the file name, and the component selectors use the same idea with the component and view. The keys are read from the
template’s tpl_<template>.sys.ini language file, and the guide also describes entering them as language overrides.
; templates/cassiopeia_tallpine/language/en-GB/tpl_cassiopeia_tallpine.sys.ini
TPL_CASSIOPEIA_TALLPINE_MOD_TALLPINE_NEXTHIKES_LAYOUT_COMPACT="Compact list (Tallpine)"
TPL_CASSIOPEIA_TALLPINE_COM_CONTENT_ARTICLE_LAYOUT_TRAILREPORT="Trail report"
TPL_CASSIOPEIA_TALLPINE_COM_CONTENT_ARTICLE_TRAILMAP_TITLE="Trail map article"
TPL_CASSIOPEIA_TALLPINE_COM_CONTENT_ARTICLE_TRAILMAP_OPTION="Trail map"
TPL_CASSIOPEIA_TALLPINE_COM_CONTENT_ARTICLE_TRAILMAP_DESC="A single article shown with a map and the GPX download."
Module alternative layouts
Create html/mod_tallpine_nexthikes/compact.php (copy default.php through Create Overrides and rename it), then edit
the module in Content → Site Modules. On the Advanced tab, open the Layout list: the entry Compact list
(Tallpine) appears in the group of the cassiopeia_tallpine template. The stored value is
cassiopeia_tallpine:compact.
<?php
// templates/cassiopeia_tallpine/html/mod_tallpine_nexthikes/compact.php
defined('_JEXEC') or die;
/** @var \stdClass[] $list */
if (empty($list)) {
return;
}
?>
<p class="mod-tallpine-nexthikes mod-tallpine-nexthikes--compact">
<?php foreach ($list as $index => $hike) : ?>
<?php echo $index > 0 ? ' · ' : ''; ?>
<a href="<?php echo htmlspecialchars($hike->link, ENT_QUOTES, 'UTF-8'); ?>"><?php echo htmlspecialchars($hike->title, ENT_QUOTES, 'UTF-8'); ?></a>
<?php endforeach; ?>
</p>
A module layout applies wherever the module is shown, whichever template renders the page. The selector therefore lists the layouts of every enabled template, and a layout that relies on one template’s CSS looks wrong on pages that use another template style. Test the module on every template style that can show it.
Component alternative layouts
For articles, contacts and news feeds a single item can use an alternative layout. Place the file in
html/com_content/article/; here, trailreport.php:
<?php
// templates/cassiopeia_tallpine/html/com_content/article/trailreport.php
defined('_JEXEC') or die;
use Joomla\CMS\Layout\LayoutHelper;
/** @var \Joomla\Component\Content\Site\View\Article\HtmlView $this */
?>
<article class="tallpine-trail-report">
<h1><?php echo $this->escape($this->item->title); ?></h1>
<?php echo $this->item->event->afterDisplayTitle; ?>
<?php echo $this->item->event->beforeDisplayContent; ?>
<div class="tallpine-trail-report__body">
<?php echo $this->item->text; ?>
</div>
<?php echo LayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
<?php echo $this->item->event->afterDisplayContent; ?>
</article>
To use it, either set the default for every article in Content → Articles → Options, field Choose a Layout, or open
one article and select the layout in its Options tab. The choices are Use Global, From Component’s Default and one
group per template. The layout is used only if no menu item targets that particular article: a menu item of type
Single Article beats the layout chosen on the article, and a menu item layout beats both. The same applies to
categories: put the file in the category folder (html/com_content/category/), select it in the component Options or
in the category’s Options tab, and note that articles, banners, contacts and news feeds support it. Blog and List
are the core category layouts; an alternative layout is listed next to them.
Do not place an XML file with the same base name in the component or category folder unless you mean it, because an XML file turns the layout into a menu item type.
Menu item layouts
A layout with an XML file next to it becomes a menu item type. The Tallpine board wants a menu entry that shows one
article as a trail map page. The pair of files is trailmap.php and trailmap.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!-- templates/cassiopeia_tallpine/html/com_content/article/trailmap.xml -->
<metadata>
<layout title="TPL_CASSIOPEIA_TALLPINE_COM_CONTENT_ARTICLE_TRAILMAP_TITLE" option="TPL_CASSIOPEIA_TALLPINE_COM_CONTENT_ARTICLE_TRAILMAP_OPTION">
<message>
<![CDATA[TPL_CASSIOPEIA_TALLPINE_COM_CONTENT_ARTICLE_TRAILMAP_DESC]]>
</message>
</layout>
<!-- Request variables of the layout: the article to show. -->
<fields name="request">
<fieldset name="request" addfieldprefix="Joomla\Component\Content\Administrator\Field">
<field
name="id"
type="modal_article"
label="COM_CONTENT_FIELD_SELECT_ARTICLE_LABEL"
required="true"
select="true"
new="true"
edit="true"
clear="true"
/>
</fieldset>
</fields>
</metadata>
The structure follows the core components/com_content/tmpl/article/default.xml: a metadata root, a layout element
with the title and option language keys, an optional help and message, and fields sections for the request variables
and the parameters. A template_style_id field with a template attribute limits the menu item type to one template’s
styles. The new type appears when you choose a Menu Item Type for a new menu item. Four rules apply:
-
A menu item layout beats component and category layouts.
-
Template overrides do not apply to a menu item layout: the file is used as it is.
-
Parameters you add to the XML are available in the layout file, but the core model and view do not process them.
-
Translate the title, option and description with the key pattern
TPL_<template>_<component>_<view>_<menu item name>_<TITLE|OPTION|DESC>.
The table summarises the effect of each choice.
| Layout kind | Selected in | Precedence |
|---|---|---|
Template override ( |
Nowhere; always on |
Replaces the core file for every use |
Module alternative layout |
Module, Advanced tab, Layout |
Per module instance, in any template that shows it |
Article, contact or news feed layout |
Component Options, or the item’s Options tab |
Used unless a menu item targets that item |
Category layout |
Component Options, or the category’s Options tab |
Used unless a menu item targets that category |
Menu item layout (with XML) |
Menu Item Type |
Beats component and category layouts; not overridable |
The custom-fields layout for Tallpine trail data
Custom fields reach a page in three ways, and each can be overridden. The field system is described in
Joomla custom fields; this section shows the template side for the trail fields
(distance-km, elevation-gain, difficulty, gpx-file, season).
-
Automatic Display. A field set to After Title, Before Display Content or After Display Content is printed by the fields system plugin into
$this->item->event->afterDisplayTitle,beforeDisplayContentorafterDisplayContent, so the position in the page comes from where the view prints those three variables. -
Direct placement. The field is set to Do not automatically display and the view override prints it with
FieldsHelper::render()(the article override above does this).$this->item->jcfieldsholds an object per field withid,name,label,type,params,context,valueandrawvalue. PHP property names are case-sensitive, so spell itjcfieldseverywhere. -
Wrapper markup. Whichever way a field is printed, its label, prefix, value and suffix come from the layout
field.render. Overriding it changes every field at once.
FieldsHelper::render($context, 'field.render', ['field' => $field]) first renders the layout with the component taken
from the context (com_content for com_content.article) and, if that produced nothing, renders it again with
com_fields. In a template that means the first existing file of these wins: html/layouts/com_content/field/render.php
in the child, then in the parent, then components/com_content/layouts/field/render.php, then
html/layouts/com_fields/field/render.php, and finally components/com_fields/layouts/field/render.php. The core
layout prints the label (span.field-label) when Show Label is on, the prefix, the value (span.field-value) and the
suffix, and returns nothing for an empty value. The override below adds a class and a data attribute per field name that
the Tallpine stylesheet uses to draw the difficulty badge.
<?php
// templates/cassiopeia_tallpine/html/layouts/com_fields/field/render.php
// Based on components/com_fields/layouts/field/render.php
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
if (!isset($displayData['field'])) {
return;
}
$field = $displayData['field'];
$value = $field->value;
if ($value === '' || $value === null) {
return;
}
$label = Text::_($field->label);
$showLabel = (int) $field->params->get('showlabel') === 1;
$prefix = $field->params->get('prefix') ? Text::plural($field->params->get('prefix'), $value) : '';
$suffix = $field->params->get('suffix') ? Text::plural($field->params->get('suffix'), $value) : '';
$labelClass = $field->params->get('label_render_class');
$valueClass = $field->params->get('value_render_class');
$name = htmlspecialchars($field->name, ENT_QUOTES, 'UTF-8');
?>
<span class="tallpine-field tallpine-field--<?php echo $name; ?>" data-field="<?php echo $name; ?>">
<?php if ($showLabel) : ?>
<span class="field-label <?php echo htmlspecialchars((string) $labelClass, ENT_QUOTES, 'UTF-8'); ?>"><?php echo htmlspecialchars($label, ENT_QUOTES | ENT_IGNORE, 'UTF-8'); ?>: </span>
<?php endif; ?>
<?php if ($prefix !== '') : ?>
<span class="field-prefix"><?php echo htmlspecialchars($prefix, ENT_QUOTES | ENT_IGNORE, 'UTF-8'); ?></span>
<?php endif; ?>
<span class="field-value <?php echo htmlspecialchars((string) $valueClass, ENT_QUOTES, 'UTF-8'); ?>"><?php echo $value; ?></span>
<?php if ($suffix !== '') : ?>
<span class="field-suffix"><?php echo htmlspecialchars($suffix, ENT_QUOTES | ENT_IGNORE, 'UTF-8'); ?></span>
<?php endif; ?>
</span>
The field value is printed unescaped on purpose, because the type plugin has already prepared it (an Editor field is
HTML, the others are encoded); an override that escapes it again shows entities, and one that prints rawvalue must
escape it. An override of the front-end layout changes only the front-end display; the edit forms in the administrator
use their own layouts.
Keeping the "override changed after core update" list under control
Copying a core file freezes its logic at the version of the day. When a later update fixes a bug, closes a security hole or changes the variables the file uses, the override keeps the old code. Joomla tracks this with three parts.
-
The installer plugin Installer - Override (
plg_installer_override) listens to the events around a Joomla update, an extension update and an extension installation. Before the event it records, in the session, a fingerprint of the core file behind each file in thehtmlfolders of the enabled templates; after the event it records them again. The files whose original changed are written to the table#__template_overridesand the administrator sees a message "N files that you have an override for in your template have been updated and you should review those changes" with a link to the Templates manager. -
The tab Updated Files in Templates: Customise lists each file with the columns Checked, Template File, Added to the list, Last change via Update and Update Source. A badge Original file removed marks an override whose original no longer exists. When nothing changed the tab reads "Override files are up to date. Nothing has been changed in the last extension or Joomla update."
-
The quick-icon plugin Quick Icon - Joomla! Overrides Update Notification (
plg_quickicon_overridecheck) adds a notification to the Home Dashboard of users who may manage the Templates component. It asks the server through AJAX and prints "Override(s) to check!" with the number of files. Its description warns that the installer override plugin must be enabled to see results.
Reviewing the list is a short routine:
-
Open System → Site Templates, then Details and Files of
cassiopeia_tallpine, then the Updated Files tab. -
Click a file in the list. The editor opens it with the panes Original and Override. Use Show Differences (inline or side by side) to read what changed in the core file.
-
Merge the changes you need into the override, or remove the override if the core markup now does what you wanted.
-
Select the rows and use Mark Checked; Mark Unchecked undoes it.
The table has one row per file with the columns template, hash_id (the base64 of the relative path), extension_id,
state (0 unchecked, 1 checked), action, client_id, created_date and modified_date.
The checker is useful but narrow. It looks at the original that has the same path as an override, so a file with no
counterpart in core, such as compact.php or trailreport.php, is never flagged, even if the code it was based on
changed. It depends on the plugin being enabled when the update runs, and the before/after snapshots are kept in the
session of the administrator who runs the update. Treat it as an aid and keep your own diff in the release routine (see
the script below). In practice, enable both plugins on every site that has overrides, mark the list checked after each
review, and look at the dashboard icon after every update.
Older books and tutorials describe a manual routine: remember which files you copied and compare them yourself after
every update. Joomla! 4 Masterclass (2023) and Developing Extensions for Joomla! 5 (2023) cover overrides and child
templates, but they predate several details of Joomla 6. The Updated Files list, the Show Differences panes and the
dashboard notification date from Joomla 4.0, and the old component layout (views/article/tmpl) is now tmpl/article.
Child templates are not "brand new" either: they have been the standard way to customise Cassiopeia since 4.1, and Joomla
6.0 ships cassiopeia_extended as a core child template.
Keeping overrides maintainable
An override costs a small amount of work on every update. The aim is to have as few as possible and to make the remaining ones easy to review.
-
Override the smallest thing. Prefer a layout or a sub-layout over a whole view, and a CSS or Web Asset change over both. If a change is only a class name, the Render Class options of a custom field or a CSS rule in the child template do the job without any override.
-
Keep overrides in the child template, never in Cassiopeia. Files in
templates/cassiopeiaare replaced by every core update (see Child templates). Overrides in the child survive, and the parent continues to provide the rest. -
Delete files you do not change. Copying a whole view folder leaves a dozen files that need a review after every update. Delete the unchanged ones; the component supplies them.
-
Mark the base version. A comment at the top of each override, such as "Based on the article view of Joomla 6.2", shows which core version the copy follows.
-
Put the template in version control. The
htmlfolder belongs in the same repository ascassiopeia_tallpine, sogit diffshows exactly what a change to the override did. -
Review after every update, in CI. The script below lists each override together with its core original so a person can compare them.
-
Remove overrides that no longer pay for themselves. After an update, if the core file already does what the override did, delete the override.
-
Remove the timestamped copies. Files named like
default-20261030-101500.phpare not loaded.
#!/usr/bin/env bash
# tools/check-overrides.sh -- run from the Joomla root; lists overrides whose core original differs from the override.
set -euo pipefail
HTML=templates/cassiopeia_tallpine/html
map_core() {
# Map an override path to the file it was copied from
case "$1" in
"$HTML"/layouts/com_fields/*) echo "components/com_fields/layouts/${1#"$HTML"/layouts/com_fields/}" ;;
"$HTML"/layouts/*) echo "layouts/${1#"$HTML"/layouts/}" ;;
"$HTML"/mod_*) m=${1#"$HTML"/}; echo "modules/${m%%/*}/tmpl/${m#*/}" ;;
"$HTML"/plg_*) p=${1#"$HTML"/}; n=${p%%/*}; g=${n#plg_}; g=${g%%_*}; e=${n#plg_${g}_}; echo "plugins/$g/$e/tmpl/${p#*/}" ;;
"$HTML"/com_*) c=${1#"$HTML"/}; echo "components/${c%%/*}/tmpl/${c#*/}" ;;
esac
}
status=0
while IFS= read -r -d '' file; do
core=$(map_core "$file" || true)
if [ -n "${core:-}" ] && [ -f "$core" ]; then
if ! diff -q "$core" "$file" > /dev/null; then
echo "DIFFERS $file <- $core"
status=1
fi
else
echo "NO CORE $file (alternative layout or moved original)"
fi
done < <(find "$HTML" -name '*.php' -print0)
exit $status
The script reports a difference for every override, because an override is supposed to differ; its value is the list it
prints for a person to check against the release notes, and its exit status can be ignored in a pipeline that only
reports. A more selective variant stores the hash of each core original at the time of copying (for example in an
overrides.lock file next to the template) and fails when the hash of the current core file is different. That is the
same idea as the Updated Files tab, but it runs in CI before anything is deployed.
Common errors and pitfalls
| Symptom | Likely cause and fix |
|---|---|
The override has no effect |
Wrong folder or file name (case matters on Linux), a different template style assigned to the menu item, an extension
that does not use the standard lookup, or cached output. Use the layout |
Plugin output has disappeared |
The override omitted |
A blank area where a module should be |
The module layout returned early ( |
|
The override uses the |
The alternative layout is not in the selector |
The file name contains an underscore, the template is disabled, the file is only in the parent template, or the extension has no selector (plugins). |
A menu item shows other markup than the article’s own choice |
A menu item layout, or a menu item that targets the article, takes precedence over the article’s own choice. |
The selector shows a raw language key or a file name |
The |
Escaped entities in a custom field |
The override escaped |
Security-wise, an override is ordinary PHP in the web root. It runs with the rights of the web server, can read request
data and the database, and is not reviewed by the update process. Treat the template like application code: keep it in
version control, escape every value that comes from users or from fields, keep the defined('_JEXEC') or die; guard,
avoid file or network access in markup, and restrict who may edit template files in the Templates component, because a
user who can edit them can run PHP. File permissions of the templates folder follow the hardening advice in
Security hardening, MFA and passkeys.
Version notes
-
Joomla 3.2 introduced the Create Overrides tab; the layout search with the
componentoption dates from 3.5. -
Joomla 4.0 added the Updated Files list, the Installer - Override and Quick Icon plugins, the
tmplfolder directly below the component, and made Bootstrap 5 the base of the core markup, so older overrides that use Bootstrap 2 or 3 classes need rework. -
Joomla 4.1 added child templates; 6.0 ships
cassiopeia_extendedas a core child template. -
Joomla 5.0 removed ES5 assets, so an override that registers an old script file needs the ES6 asset.
-
Joomla 6.0 removed the
J*class aliases by default,JPATH_PLATFORMand$this->dbin views; they return only through Behaviour - Backward Compatibility 6. The plugin is meant as a bridge, not as a permanent setting. -
Joomla 6.1 added the
"version": "nocache"option for assets, which helps while developing the assets that an override loads.