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

components/com_content/tmpl/article/default.php (site)

templates/cassiopeia_tallpine/html/com_content/article/default.php

Module

modules/mod_tallpine_nexthikes/tmpl/default.php

templates/cassiopeia_tallpine/html/mod_tallpine_nexthikes/default.php

Plugin

plugins/content/pagenavigation/tmpl/default.php

templates/cassiopeia_tallpine/html/plg_content_pagenavigation/default.php

Layout (shared fragments)

layouts/joomla/content/info_block.php, components/com_fields/layouts/field/render.php

templates/cassiopeia_tallpine/html/layouts/joomla/content/info_block.php, templates/cassiopeia_tallpine/html/layouts/com_fields/field/render.php

A few conventions are worth remembering:

  • The folder under html is the extension’s technical name: com_, mod_ or plg_<group>_<name>. For a plugin the folder name joins the group and the plugin name, so plugins/content/pagenavigation becomes html/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 use views/<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.php of 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 tmpl folder, 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.

flowchart TD S[Extension asks for a template file] --> K{Which family?} K -- component view --> V1[html/com_x/view in the child template] V1 -- missing --> V2[html/com_x/view in the parent template] V2 -- missing --> V3[tmpl folder of the component] K -- module --> M1[html/mod_x/layout.php in the child template] M1 -- missing --> M2[html/mod_x/layout.php in the parent template] M2 -- missing --> M3[modules/mod_x/tmpl/layout.php] M3 -- missing --> M4[modules/mod_x/tmpl/default.php] K -- plugin --> P1[html/plg_group_name in the child template] P1 -- missing --> P2[html/plg_group_name in the parent template] P2 -- missing --> P3[plugins/group/name/tmpl] K -- layout --> L0[base path passed by the caller] L0 -- missing --> L1[html/layouts/component in child, then parent] L1 -- missing --> L2[components/com_x/layouts] L2 -- missing --> L3[html/layouts in child, then parent] L3 -- missing --> L4[layouts in the Joomla root] V1 -- found --> OUT[Include the first file found] V2 -- found --> OUT V3 -- found --> OUT M1 -- found --> OUT M2 -- found --> OUT M3 -- found --> OUT M4 -- found --> OUT P1 -- found --> OUT P2 -- found --> OUT P3 -- found --> OUT L0 -- found --> OUT L1 -- found --> OUT L2 -- found --> OUT L3 -- found --> OUT L4 -- found --> OUT

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:

  1. a base path passed by the caller (module and plugin layouts use this to point at their own folder)

  2. html/layouts/<component> in the active template, then in the parent, when a component option is given

  3. the component’s own layouts folder, in components/<component>/layouts

  4. html/layouts in the active template, then in the parent

  5. layouts in 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.

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.

  1. Open System → Site Templates and click the Details and Files link of the template (for Tallpine, the cassiopeia_tallpine entry). The Templates: Customise screen has the tabs Editor, Create Overrides, Updated Files, Description and the file tree.

  2. 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.

  3. Joomla copies the PHP files of that view into the template’s html folder, shows a message such as "Override created in /templates/cassiopeia_tallpine/html/com_content/article" and returns to the Editor tab.

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

  5. 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, beforeDisplayContent and afterDisplayContent are 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, while rawvalue is 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() and useScript() and register the assets in the template’s joomla.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, JFactory or JLayoutHelper. Those J* 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 with Joomla\CMS\Language\Text, Joomla\CMS\HTML\HTMLHelper, Joomla\CMS\Router\Route, Joomla\CMS\Factory and Joomla\CMS\Layout\LayoutHelper. Likewise, guard the file with defined('_JEXEC') or die;, because Joomla 6 no longer defines JPATH_PLATFORM unless 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>

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 example html/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']
);

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.php and trailreport.php are alternative layouts; default.php is the override of the default layout. The module layout selector lists only files whose name contains no underscore, because name_part.php is 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 ? ' &middot; ' : ''; ?>
        <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 (default.php)

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

  1. 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, beforeDisplayContent or afterDisplayContent, so the position in the page comes from where the view prints those three variables.

  2. 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->jcfields holds an object per field with id, name, label, type, params, context, value and rawvalue. PHP property names are case-sensitive, so spell it jcfields everywhere.

  3. 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 the html folders of the enabled templates; after the event it records them again. The files whose original changed are written to the table #__template_overrides and 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.

flowchart LR U[Core or extension update] --> B[Installer - Override stores fingerprints before] B --> I[Files are replaced] I --> A[Installer - Override stores fingerprints after] A --> C{Original of an override changed?} C -- no --> N[Nothing is recorded] C -- yes --> R[Row in the template_overrides table, state unchecked] R --> M[Message after the update and quick icon on the dashboard] R --> T[Updated Files tab in the Templates manager] T --> D[Show Differences between original and override] D --> E[Edit the override or accept it] E --> K[Mark Checked]

Reviewing the list is a short routine:

  1. Open System → Site Templates, then Details and Files of cassiopeia_tallpine, then the Updated Files tab.

  2. 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.

  3. Merge the changes you need into the override, or remove the override if the core markup now does what you wanted.

  4. 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/cassiopeia are 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 html folder belongs in the same repository as cassiopeia_tallpine, so git diff shows 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.php are 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 debug option for layouts, and check the style assignment.

Plugin output has disappeared

The override omitted afterDisplayTitle, beforeDisplayContent or afterDisplayContent, or removed a variable the plugin sets.

A blank area where a module should be

The module layout returned early (empty($list)), or the selected layout belongs to a template that is not active on that page.

Class "JText" not found or a fatal error after moving to Joomla 6

The override uses the J* aliases or JPATH_PLATFORM, available only through the backward-compatibility plugin. Convert to namespaced classes and defined('_JEXEC') or die;.

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 TPL_<TEMPLATE>_<MODULE>_LAYOUT_<NAME> key is missing from tpl_<template>.sys.ini or the language overrides.

Escaped entities in a custom field

The override escaped $field->value, which is already prepared; escape rawvalue only.

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 component option dates from 3.5.

  • Joomla 4.0 added the Updated Files list, the Installer - Override and Quick Icon plugins, the tmpl folder 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_extended as 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_PLATFORM and $this->db in 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.