Joomla Layouts and Module Chromes

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 layout is a small PHP file that turns an array of data into HTML. Joomla uses layouts for the building blocks that many screens share — an image tag, a pagination bar, the article information block, the system message area, form fields, toolbar buttons — and for the wrapper that surrounds every module, the module chrome. Layouts are the cheapest part of Joomla to customise: a single file dropped into a template’s html/layouts folder replaces the core file everywhere it is used, with no change to the extension that calls it. This page explains how the layout engine resolves a layout, which folders it searches, how overrides and sublayouts work, how a module chrome is chosen and written, and what the jdoc:include statements in a template do.

It was checked against the official documentation and the 6.2 source tree on 2026-10-30. The behaviour described is that of Joomla 6.2 (current, requiring PHP 8.3 or newer). The layout classes, Joomla\CMS\Layout\FileLayout and Joomla\CMS\Layout\LayoutHelper, date from Joomla 3.0 and 3.1 and have kept the same public shape through 4.x, 5.x and 6.x, so the techniques here also apply to Joomla 5.4, which now receives security fixes only. Joomla 4.x and 3.x are end of life.

The examples belong to the Tallpine Hiking Club, the fictitious mountain club used across this sub-section. Its site uses the child template cassiopeia_tallpine (see Child templates) and the component com_tallpine. The general idea of module positions is covered in Modules and positions; this page goes deeper into the layout engine behind the module styles, and the whole override family is surveyed in Template overrides and alternative layouts.

Who works with layouts

  • Themers override layouts in a child template to change markup without touching extensions, and write module chromes.

  • Site builders pick a module style for each module on the Advanced tab and place jdoc:include statements through the positions a template offers.

  • Extension developers ship layouts inside components, modules and plugins, so that themers can override them. Component views are covered in Component site views and routing and modules in Module development.

  • Administrators care mostly about the security and caching consequences covered at the end of the page.

Layouts versus view templates

Joomla has two override mechanisms that are easy to confuse.

  • A view template (tmpl file) belongs to one view of one component, for example the article view of com_content. It is overridden in templates/<template>/html/com_content/article/default.php.

  • A layout is a reusable fragment with an identifier such as joomla.content.info_block. It is called by LayoutHelper::render() from view templates, from modules, from plugins and from the core renderers. It is overridden in templates/<template>/html/layouts/joomla/content/info_block.php.

The word layouts in the path is therefore the signal: anything below html/layouts is a layout, and anything else below html is a view template or a module template. A view template that is overridden in a child template still calls the original layouts, so the two mechanisms combine.

Older books such as Joomla! 4 Masterclass (2023) and Developing Extensions for Joomla! 5 (2023) cover overrides and child templates well, but they describe the layout engine only in passing. They also treat child templates as new (Joomla 4.1); in Joomla 6 a child template is the normal way to customise Cassiopeia, and Joomla 6.0 ships cassiopeia_extended as a core child template.

LayoutHelper::render()

The everyday entry point is a static helper:

use Joomla\CMS\Layout\LayoutHelper;

echo LayoutHelper::render($layoutId, $displayData, $basePath, $options);
  • $layoutId is a dot-separated identifier. Each dot becomes a directory separator and .php is appended, so joomla.html.image is the file layouts/joomla/html/image.php.

  • $displayData is the data the layout needs. Inside the layout file it is available as $displayData.

  • $basePath is optional. When it is not empty it is placed at the top of the list of folders searched. When it is empty, the static property LayoutHelper::$defaultBasePath is used instead.

  • $options is an optional array (see Render options).

render() creates a FileLayout, passes it the same arguments and returns its output as a string. The helper has a sibling, LayoutHelper::debug(), with the same arguments, which renders the layout with the debug output described later.

A first example uses a core layout, the image tag. The layout reads src, escapes src and alt, adds width and height when it can read the image size and adds loading="lazy".

<?php
defined('_JEXEC') or die;

use Joomla\CMS\Layout\LayoutHelper;

// A hut photo on a Tallpine trail page.
echo LayoutHelper::render('joomla.html.image', [
    'src'   => 'images/trails/alpine/stormcloud-hut.jpg',
    'alt'   => 'Stormcloud Hut at sunrise',
    'class' => 'img-fluid trail-photo',
]);

The layout’s own header comment states the keys it expects, which is where to look when you reuse a core layout. The comment also warns that only alt and src are escaped by default for the image layout; the layout escapes every attribute it prints, but a layout that does not must be fed escaped data. As a rule, a layout escapes at the point of output, and the caller passes raw values.

FileLayout

Joomla\CMS\Layout\FileLayout does the work. Use it directly when LayoutHelper::render() is not enough, for example to add a search folder, to set suffixes, or to render the same layout several times with different data.

<?php
defined('_JEXEC') or die;

use Joomla\CMS\Layout\FileLayout;

$layout = new FileLayout('com_tallpine.trail.card', null, ['component' => 'com_tallpine']);
$layout->addIncludePath(JPATH_ROOT . '/tallpine-shared/layouts');

foreach ($this->items as $trail) {
    echo $layout->render(['trail' => $trail, 'params' => $this->params]);
}

The public methods worth knowing are:

  • render($displayData) returns the layout output as a string. The output is captured with output buffering around include, so the layout file may print with echo or inline HTML. Data given earlier with setData() is merged with $displayData when the latter is an array.

  • addIncludePath($path) and addIncludePaths($paths) put one folder, or several, at the front of the search list. setIncludePaths() replaces the list, removeIncludePath() removes folders and clearIncludePaths() empties the list so that the defaults are recomputed.

  • getIncludePaths() returns the folders in the order they are searched. It is a quick way to inspect the result in a debugger.

  • setSuffixes(), loadLanguageSuffixes() and loadVersionSuffixes() set the file-name variants to try.

  • setComponent() and setClient() change which component’s folders and which application’s (site or administrator) template are searched.

  • sublayout($layoutId, $displayData) renders a subsidiary layout from inside a layout; see Sublayouts.

  • escape($output), inherited from BaseLayout, applies htmlspecialchars(). Layouts commonly call $this->escape($value).

  • get() and set() read and write values on the layout object, and getOptions() returns the options as a Registry. Inside a layout file $this is the FileLayout instance, so options are reachable as $this->options->get('client').

A layout is resolved once per combination of layout id, search folders and suffixes in a static in-memory cache, so calling the same layout in a loop does not search the file system again for every item.

The layout folder tree

Core layouts live in the top-level layouts folder of the installation. In 6.2 its first level is:

  • layouts/joomla/ — the shared building blocks, in turn split into folders such as button, content, edit, editors, error, form, html, icon, installer, links, mail, pagination, quickicons, searchtools, sidebars, system, tinymce and toolbar

  • layouts/chromes/ — the system module styles html5.php, none.php, outline.php and table.php

  • layouts/libraries/ — layouts used by the HTML helpers, for example the Bootstrap modal parts

  • layouts/plugins/ — layouts that belong to core plugins, for example the privacy consent, WebAuthn, terms and token plugins and the TinyMCE editor

Some of the layouts most often overridden are:

Layout id What it draws

joomla.content.info_block and its sublayouts under joomla/content/info_block/

The article information list: author, category, parent category, dates, hits and associations

joomla.content.tags, joomla.content.readmore, joomla.content.intro_image, joomla.content.full_image

Tag list, the "Read more" link and the article images

joomla.content.icons and joomla.content.icons/*

The article edit, create and lock icons

joomla.pagination.links, joomla.pagination.list, joomla.pagination.link

The pagination bar

joomla.system.message

The system message area printed by jdoc:include type="message"

joomla.html.image

An escaped img tag

joomla.form.field.*

Individual form field wrappers

chromes.*

Module styles; see Module chromes

Components and extensions add their own folders. A component keeps layouts in a layouts folder in its site or administrator folder (components/com_tallpine/layouts), and by convention each extension puts its layouts in a subfolder named after the extension to avoid file-name clashes. The layout id for components/com_tallpine/layouts/com_tallpine/trail/card.php is com_tallpine.trail.card.

Modules and plugins are different. Joomla does not search their folders unless the caller says so, because a module’s layouts are not meant to be overridden through the component rules. A module that wants to use its own layout folder passes it as $basePath:

echo LayoutHelper::render(
    'mod_tallpine_nexthikes.hike',
    ['hike' => $hike],
    JPATH_ROOT . '/modules/mod_tallpine_nexthikes/layouts'
);

The search order

When FileLayout needs a layout it builds the list of folders in getDefaultIncludePaths() and uses the first folder that contains the file. In the 6.2 source the order, from highest to lowest priority, is:

  1. $basePath, when the caller provided one.

  2. If a component applies: the component override folder templates/<template>/html/layouts/<component> in the active template, then the same folder in the parent template when the active template is a child.

  3. If a component applies: the component’s own layouts folder (site or administrator, chosen by the client option).

  4. The general override folder templates/<template>/html/layouts in the active template, then in the parent template.

  5. The core layouts folder.

Seven directories searched in order for a component layout, with a child template

Three details matter in practice.

  • A component applies when the component option names an enabled component, or, by default (auto), when a component is the one being dispatched in the current request. In a module or plugin that is not rendered by that component, pass 'component' => 'com_tallpine' to include its folders, or 'component' => 'none' to skip the component folders entirely.

  • The user-facing manual lists four directories. The parent template entries are added by the 6.2 source for child templates and are what make a child template inherit overrides from Cassiopeia: a layout overridden in the parent keeps working, and the child overrides it in turn.

  • The client option decides whether the site or the administrator template is consulted: 0 or site, 1 or admin; the default follows the application that is running. An administrator layout is therefore overridden in administrator/templates/atum/html/layouts, not in the site template.

Overriding a layout

To override a layout, copy the core file into the active template at the same relative path under html/layouts and edit the copy. The override takes precedence for every caller, so it is a sharp tool; keep the copy small and make the diff with the core file easy to review.

The Tallpine club wants the article information block on a trail report to drop the page-hit counter. The hits item is a sublayout, so only that one file is overridden:

<?php
// templates/cassiopeia_tallpine/html/layouts/joomla/content/info_block/hits.php
defined('_JEXEC') or die;
// The Tallpine site shows no hit counter.
return;

Because info_block.php calls $this->sublayout('hits', $displayData), and sublayouts use the same search folders as their parent, this tiny file replaces joomla/content/info_block/hits.php without a copy of the other seven sublayouts. Leaving the file empty after the guard is valid: the layout simply produces no output.

The same technique restyles the system message area: the joomla.system.message layout is the one jdoc:include type="message" prints, so an override of joomla/system/message.php changes every flash message. Start from a copy of the installed version of that file, because it also carries the markup the Joomla alert script expects.

The Joomla Manual describes this mechanism as the template override functionality, and the Administrator offers a wizard for it: open the template under System → Site Templates, and its Create Overrides tab lists components, modules, plugins and layouts that can be copied into the template. See Template overrides and alternative layouts for the screens.

Keeping overrides healthy

  • Copy from the installed version. An override is a frozen copy. When a core update changes the original, Joomla marks the override in the Overrides view of the template manager with a "changed" flag, and the copy needs reviewing. A stale override can lose security fixes (for example output escaping added in a core update), so review them after every update; the Tallpine runbook does so as part of the update routine.

  • Override the narrowest file. Prefer a sublayout like hits.php to the whole info_block.php; prefer a CSS change to a markup change; prefer a module style option to a new chrome.

  • Keep the guard line. defined('_JEXEC') or die; is the guard in Joomla 6. The older JPATH_PLATFORM constant is no longer defined in 6.0 unless the Behaviour - Backward Compatibility 6 plugin is enabled, so an override that still tests JPATH_PLATFORM dies on a clean 6.x install.

  • Escape what you print. A layout is output code. Use htmlspecialchars() or $this->escape() for text values and ArrayHelper::toString() or the HTMLHelper helpers for attribute lists; do not echo request input.

Sublayouts

$this->sublayout('name', $data) renders a file from a folder named after the parent layout file. From layouts/joomla/content/info_block.php the call $this->sublayout('author', $displayData) loads layouts/joomla/content/info_block/author.php. The parent’s basePath, options and include folders are reused, so a sublayout can be overridden on its own, as the hits.php example showed. The data passed may be the whole $displayData or a part of it.

The Tallpine trail card is a good use. The layout com_tallpine.trail.card prints a trail and delegates the difficulty badge to a sublayout:

<?php
// components/com_tallpine/layouts/com_tallpine/trail/card.php
defined('_JEXEC') or die;

$trail = $displayData['trail'];
?>
<article class="card trail-card h-100">
    <div class="card-body">
        <h2 class="card-title h5"><?php echo $this->escape($trail->title); ?></h2>
        <?php echo $this->sublayout('badge', ['difficulty' => $trail->difficulty]); ?>
        <p class="card-text">
            <?php echo $this->escape($trail->distance_km); ?> km,
            <?php echo (int) $trail->elevation_gain; ?> m
        </p>
    </div>
</article>
<?php
// components/com_tallpine/layouts/com_tallpine/trail/card/badge.php
defined('_JEXEC') or die;

$levels = ['easy' => 'bg-success', 'moderate' => 'bg-warning text-dark', 'hard' => 'bg-danger'];
$level  = $displayData['difficulty'] ?? 'easy';
?>
<span class="badge <?php echo $levels[$level] ?? 'bg-secondary'; ?>">
    <?php echo $this->escape(ucfirst($level)); ?>
</span>

A themer can now replace only templates/cassiopeia_tallpine/html/layouts/com_tallpine/trail/card/badge.php. The array lookup keeps an unexpected value from reaching a class attribute.

Render options

The fourth argument of render(), and the third of the FileLayout constructor, is an options array. Inside the layout the options are available as a Registry at $this->options.

Option Meaning

debug

When true, the engine prints the layout id, the include folders in order, every file name tried and the file found, inside a pre element. LayoutHelper::debug() sets it for you.

client

0 or site, 1 or admin. Selects the site or the administrator template and component folders.

component

A component name such as com_tallpine, auto (the default; the component being dispatched) or none. Adds that component’s override and layout folders. An unknown or disabled component is dropped silently.

suffixes

An array of suffixes tried before the plain file name.

template

A template name to use instead of the active one, for rendering with another template’s overrides.

Suffixes

With 'suffixes' => ['ltr'], the engine looks in each folder for div.ltr.php before div.php. The FileLayout methods loadLanguageSuffixes() (language tag such as de-DE, the language part de, and rtl or ltr) and loadVersionSuffixes() (for example j62, j6) build suffix lists from the language and from the Joomla version, so that one folder can hold card.de-DE.php, card.rtl.php and card.php. Suffixes are an escape hatch: for most sites a language-aware layout reads the language strings instead (see Language files and internationalisation).

Debugging a layout

To find out which file wins, turn debugging on for one call:

echo LayoutHelper::render('com_tallpine.trail.card', $data, '', ['debug' => true, 'component' => 'com_tallpine']);

The output lists the include folders in the order of the search order figure and names the file found, which settles disputes about which override applies. Remove the option before releasing: the debug block is printed raw into the page and exposes server paths. Techniques for finding the right file are in Debugging.

Module chromes

A module chrome, called a module style in the Administrator, is a layout that receives a rendered module and returns the module wrapped in markup. It is an ordinary layout in a folder called chromes, and its id is chromes.<name>. Sections of the module style user interface — the Module Style field of the Advanced tab, the Template-style values, and the list of core module types — are covered in Modules and positions. This section covers how the chrome is selected and what the engine hands to it.

How a chrome is chosen

In ModuleHelper::renderModule() (6.2) the module is first rendered raw by its dispatcher, then wrapped:

flowchart TD J["jdoc:include type=modules name=sidebar-right style=card"] --> M[ModulesRenderer loops over the modules of the position] M --> R[ModuleRenderer renders each module] R --> D[Module dispatcher produces the raw content] D --> S{Module Style set on the Advanced tab?} S -- yes --> P[Style from the module overrides the attribute] S -- no --> A[style attribute of the jdoc:include] P --> C[LayoutHelper::render with chromes and the style name] A --> C C --> E[onRenderModule event may change the attributes] E --> X[Chrome file found in template, parent template or core] X --> O[Wrapped HTML replaces module content]

The rules, as the code applies them:

  1. If the module’s own Module Style parameter is not empty, it wins. Its value has the form Template-style; the part before the first dash names the template whose html/layouts folder supplies the chrome, and System means the core layouts. When that template is neither the active nor the System entry, its folder is passed as $basePath.

  2. Otherwise the style attribute of the jdoc:include statement is used.

  3. If there is no style at all, the style none is used.

  4. When Preview Module Positions is active (?tp=1 with the setting enabled), the word outline is appended.

  5. The style is split on spaces. For each word, LayoutHelper::render('chromes.' . $word, $displayData, $basePath) is called, and whatever it returns replaces the module content before the next word is applied. style="card outline" therefore wraps the card in the position-preview box.

  6. A chrome that returns an empty string leaves the module content as it was.

The chrome receives $displayData with three keys: module (the module record, with content, title, showtitle, position, id, module, style and others), params (a Registry of the module parameters) and attribs (the attributes written on the jdoc:include statement, plus style). The core chromes read module tag, header tag and header class from the Advanced tab through $params->get().

The core chromes

The system chromes live in layouts/chromes. Cassiopeia adds card and noCard in templates/cassiopeia/html/layouts/chromes, and a child template inherits both through the parent folder in the search order.

Chrome Behaviour in 6.2

none

Prints $displayData['module']->content and nothing else. No wrapper and no title. Cassiopeia uses it for menu, search, banner, breadcrumbs, topbar and footer positions, whose modules draw their own markup.

html5

The classic wrapper: moduletable and the module class suffix, an optional col-md-N from Bootstrap Size, the module tag and header tag from the Advanced tab, and aria-labelledby or aria-label when the tag is not a div.

table

A legacy table wrapper for old markup.

outline

The labelled box of the position preview. It registers its own stylesheet through the Web Asset Manager.

card (Cassiopeia)

A Bootstrap card. The wrapper has the classes card, the position name and the module class suffix. The heading gets card-header, or is placed inside card-body when the header class is card-title. A class attribute on the jdoc:include is appended to the wrapper.

noCard (Cassiopeia)

The same structure with the class no-card: the position class and the heading, without Bootstrap card classes.

Cassiopeia’s index.php places card on top-a, top-b, sidebar-left, sidebar-right, main-top, main-bottom, bottom-a and bottom-b, and none on the others, exactly as 6.2 ships. A site that finds the cards too heavy chooses Cassiopeia-noCard for one module on the Advanced tab; it does not have to edit the template.

Two behaviours of the core chromes are worth remembering when you write your own. Every core chrome returns immediately when the module content is empty, so an empty module leaves no empty box. And when the module tag is not div, the chrome adds aria-labelledby (title shown) or aria-label (title hidden), which keeps landmark regions distinguishable for screen-reader users; the accessibility background is in Accessibility.

Writing a custom chrome

A chrome is a PHP file named after the style, in html/layouts/chromes of the template. The Tallpine club wants a trailcard style for its "Trail conditions" module: a section with a coloured edge, a heading, and a footer line with the date, falling back gracefully for modules without a title. The file name trailcard.php becomes both the style name and, on the Advanced tab, the entry Cassiopeia_tallpine-trailcard.

<?php
// templates/cassiopeia_tallpine/html/layouts/chromes/trailcard.php
defined('_JEXEC') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\Utilities\ArrayHelper;

/** @var \stdClass $module  @var \Joomla\Registry\Registry $params  @var array $attribs */
$module  = $displayData['module'];
$params  = $displayData['params'];
$attribs = $displayData['attribs'];

// An empty module produces no box at all.
if ($module->content === null || trim((string) $module->content) === '') {
    return;
}

// Load the stylesheet only when this chrome is actually used.
Factory::getApplication()->getDocument()->getWebAssetManager()->useStyle('template.tallpine.trailcard');

$tag         = in_array($params->get('module_tag', 'section'), ['div', 'section', 'aside', 'article'], true)
    ? $params->get('module_tag', 'section') : 'section';
$headerTag   = in_array($params->get('header_tag', 'h3'), ['h2', 'h3', 'h4', 'h5', 'h6'], true)
    ? $params->get('header_tag', 'h3') : 'h3';
$suffix      = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_QUOTES, 'UTF-8');
$extraClass  = !empty($attribs['class']) ? ' ' . htmlspecialchars($attribs['class'], ENT_QUOTES, 'UTF-8') : '';

$wrapper = ['class' => 'tallpine-trailcard ' . htmlspecialchars($module->position, ENT_QUOTES, 'UTF-8') . ' ' . $suffix . $extraClass];
$heading = ['class' => 'tallpine-trailcard__title', 'id' => 'mod-' . (int) $module->id];

// Landmark naming for non-div wrappers, as the core chromes do.
if ($tag !== 'div') {
    if ($module->showtitle) {
        $wrapper['aria-labelledby'] = 'mod-' . (int) $module->id;
    } else {
        $wrapper['aria-label'] = htmlspecialchars($module->title, ENT_QUOTES, 'UTF-8');
    }
}
?>
<<?php echo $tag; ?> <?php echo ArrayHelper::toString($wrapper); ?>>
    <?php if ($module->showtitle) : ?>
        <<?php echo $headerTag; ?> <?php echo ArrayHelper::toString($heading); ?>>
            <?php echo htmlspecialchars($module->title, ENT_QUOTES, 'UTF-8'); ?>
        </<?php echo $headerTag; ?>>
    <?php endif; ?>
    <div class="tallpine-trailcard__body"><?php echo $module->content; ?></div>
    <p class="tallpine-trailcard__stamp">
        <?php echo Text::sprintf('TPL_CASSIOPEIA_TALLPINE_UPDATED', Factory::getDate()->format('Y-m-d')); ?>
    </p>
</<?php echo $tag; ?>>

The stylesheet and its language string are declared in the child template’s joomla.asset.json and language file:

{
  "$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json",
  "name": "cassiopeia_tallpine",
  "version": "1.0.0",
  "assets": [
    {
      "name": "template.tallpine.trailcard",
      "type": "style",
      "uri": "templates/cassiopeia_tallpine/css/trailcard.min.css"
    }
  ]
}
; templates/cassiopeia_tallpine/language/en-GB/tpl_cassiopeia_tallpine.ini
TPL_CASSIOPEIA_TALLPINE_UPDATED="Updated %s"

Points to notice in this chrome:

  • It returns early on empty content, like the core chromes.

  • It restricts the module tag and header tag to a whitelist instead of printing whatever is stored in the module parameters.

  • It prints $module->title escaped. The 6.2 Cassiopeia card and noCard chromes print the title as stored, which is safe only because titles are entered by administrators; a custom chrome should not rely on that, and any chrome that shows module titles from a user-editable source must escape them.

  • It does not escape $module->content, because that is the module’s own already-rendered HTML. Never pass the content through an extra escaping function, which would turn the module markup into visible text.

  • It uses the Web Asset Manager for its stylesheet, as the core outline chrome does. The asset manager prints the link in the document head once, however many modules use the chrome; the registry order and the addRegistryFile() call for module assets are in Web Asset Manager.

To use the chrome, set the module’s Module Style to Cassiopeia_tallpine-trailcard, or place it in the template for every module of a position:

<?php if ($this->countModules('trail-conditions', true)) : ?>
    <jdoc:include type="modules" name="trail-conditions" style="trailcard" class="border-start border-4 border-success" />
<?php endif; ?>

Chrome names are resolved by file, so a typo in style does not raise an error: LayoutHelper::render() returns an empty string when no file is found, the chrome step is skipped and the module appears without a wrapper. The ?tp=1 preview shows the style name actually in use, which is the quickest way to detect the problem.

Chromes and events

Two plugin events surround the chrome step. onRenderModule runs after the style has been decided and receives the module and the attribute array, so a plugin may change style or add attributes for a particular module. onAfterRenderModule runs once the content is wrapped. Plugins of the system group that rewrite module HTML should do so in these events, not by searching the final page output. See Plugin development and events for the subscriber structure and the concrete event classes that replaced Event::getArgument() arrays in 5.0.

jdoc:include statements

A jdoc statement is an element in a template file that Joomla replaces with generated output. Statements are written as <jdoc:include type="..." /> in index.php, component.php, offline.php and error.php.

type Output

metas

Meta tags, canonical links and the base head elements.

styles

Stylesheet links and inline styles, including those queued through the Web Asset Manager.

scripts

Script tags and inline scripts, including the Joomla options script.

head

The three above in one statement (metas, styles, scripts). Cassiopeia uses the three separate statements so that it can place them independently.

component

The output of the main component, the main region of the page.

message

System messages, rendered through the joomla.system.message layout.

modules

All modules published in the position given by name, each wrapped by the chrome in style.

module

A single module chosen by its extension name (for example mod_login) and optionally by title.

The element is parsed by a regular expression: type must be the first attribute, the rest are free name="value" pairs, and the element must be self-closing. Any other attribute is passed on in the attribs array to the renderer and then to the chrome, which is how class="border-start" in the example reached the chrome. A malformed element is left in the output as literal text, which is the easiest sign of a typo.

<jdoc:include type="modules" name="sidebar-right" style="card" />
<jdoc:include type="module" name="mod_login" title="Member login" style="noCard" />
<jdoc:include type="message" />
<jdoc:include type="component" />

The order of rendering

The template is first run as PHP, so conditions and countModules() calls decide which statements remain in the text. Joomla then collects the statements and renders them in a deliberate order: all modules and module statements first, the message statement second and everything else last. This lets modules queue styles, scripts and messages before the head pieces are generated, and lets the component, rendered earlier than the template, add its assets as well. A consequence is that an asset registered while a chrome is running (as the trailcard chrome does) still reaches the document head, but an asset registered after the whole page is rendered, for example in a late system plugin event, does not.

When the Joomla page cache or module cache is in use, module output may be reused. The chrome is part of the cached module output, so changing a chrome file requires clearing the cache before the change is visible. Details are in Caching and performance.

Security

  • Treat layout files as code. They run with the full privileges of the site. Keep the templates folder writable only by the deployment user, and review changes to html/layouts as code.

  • Escape at output. $this->escape() or htmlspecialchars(..., ENT_QUOTES, 'UTF-8') for text; whitelist values that become tag names, class names or attribute names, as the trailcard chrome does.

  • Never build a layout id from request data. The id is mapped straight to a file path. Use fixed ids, or look the id up in a fixed array.

  • Do not leave debug on. The debug block prints file-system paths to every visitor of the page.

  • Keep the defined('_JEXEC') or die; guard. It stops a layout file from being executed when requested directly.

  • Re-check overrides after updates. A copy of a core layout does not receive core security fixes.

Common errors and pitfalls

  • The override is ignored. Check the path under html/layouts, the file name case on a case-sensitive file system, and whether the active template is the child (the parent’s override is found only by inheritance). Enable debug for one call to see the list of folders.

  • The override applies in one place only. The component override folder html/layouts/com_tallpine is searched before the general html/layouts folder, and the layout id is appended to it, so the file for com_tallpine.trail.card is html/layouts/com_tallpine/com_tallpine/trail/card.php there but html/layouts/com_tallpine/trail/card.php in the general folder. Both work; use the general folder unless two components share a layout id.

  • An extension layout in a module is not found. Modules and plugins are not searched by default; pass $basePath.

  • The style is silently ignored. A typo in the style name, or a style that exists only in another template, leaves the module unwrapped. Use ?tp=1 to confirm.

  • jdoc:include shows up as text. The element is malformed: type is not first, a quote is missing or the closing /> is absent.

  • Old code still works only on legacy classes. Code in older books such as Developing Extensions for Joomla! 5 that calls JLayoutHelper or JLayoutFile relies on the J* aliases, which Joomla 6 enables only through the Behaviour - Backward Compatibility 6 plugin (off on new installs). Use the namespaced classes shown above.

  • A chrome wraps an empty module. Return early on empty content, otherwise the page shows empty boxes.