Templates and Template Styles
|
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 Joomla template is the extension that turns the output of the main component and of every module into a complete
HTML page. A template style is a saved set of that template’s parameters (a logo, a colour scheme, a font choice)
plus the information about where it applies. One template can have many styles, and that split is what lets a single
installed template look different on the home page, in the member area and in the German section of a site. This page
explains both halves for the people who work with them: site builders and administrators, who create styles and assign
them to menu items, and themers, who write templateDetails.xml, index.php and the other entry files.
It was written against Joomla 6.2 (current, requiring PHP 8.3 or newer) and checked against the official
documentation and the 6.2 source tree on 2026-10-30. Joomla 5.4 only receives security fixes and Joomla 4.x and 3.x are
end of life; where the 5.x behaviour differs the text says so. The examples belong to the Tallpine Hiking Club, the
fictitious mountain club used across this sub-section, whose site runs the child template cassiopeia_tallpine of
Cassiopeia. A child template is only introduced here as far as templateDetails.xml needs it; its mechanics are in
Child templates, the Cassiopeia internals are in
Cassiopeia and Cassiopeia Extended, and a template written
from an empty folder is in Building a template from scratch.
Site templates and administrator templates
Joomla runs two applications and each has its own template. Which one is meant is stored as a client_id (0 for the
site, 1 for the administrator) and written as client="site" or client="administrator" in the manifest.
| Site template | Administrator template | |
|---|---|---|
Shipped with Joomla |
Cassiopeia, and since Joomla 6.0 the child template Cassiopeia Extended |
Atum |
|
|
|
PHP files |
|
|
Public assets (copied from the |
|
|
Management screen |
System → Site Templates and Site Template Styles |
System → Administrator Templates and Administrator Template Styles |
Entry files |
|
|
Both kinds are ordinary extensions of type template (see
Extension types, manifests and packaging): they are
installed, updated and uninstalled like any other extension, and their row in the #__extensions table carries the
type, element (the folder name) and client_id. The Administrator template is covered in
The Administrator template Atum; the rest of this page concentrates
on site templates because that is what visitors see, but template styles, templateDetails.xml and jdoc:include
work identically in the Administrator.
A third, hidden template is worth knowing. The folder templates/system is not selectable in the Administrator. It
holds the last-resort copies of index.php, component.php, error.php and offline.php, used when a template
lacks a file and nothing else can supply it (see How Joomla finds the file to render).
Who does what
-
Site builders create and duplicate styles, choose the default one and assign styles to menu items.
-
Administrators decide who may edit templates (the
com_templatespermissions), turn the position preview on and off, and keep the template files writable only by deployment. -
Themers write the manifest, the entry files, the parameters and the assets.
-
Developers of extensions only need the template to output the right positions and the
componentandmessageincludes.
Template styles
Every template has at least one style, created when the template is installed (its title is "<template> - Default").
A style is one row in the #__template_styles table:
| Column | Meaning |
|---|---|
|
The primary key, which menu items reference |
|
The template’s folder name, for example |
|
0 for the site, 1 for the administrator |
|
|
|
The name shown in the Administrator, for example "Tallpine - Default" |
|
Copy of the manifest’s |
|
The parent template’s folder name for a child template, otherwise empty |
|
The saved parameters, as JSON, built from the |
The inheritable and parent columns exist since Joomla 4.1, the release that introduced child templates. The
default data of a fresh 6.2 install contains four rows: Atum (administrator, default), Cassiopeia (site, default,
inheritable 1) and Cassiopeia Extended (site, parent set to cassiopeia, not default).
The Template Styles screens
System → Site Template Styles lists the styles with their title, the template, the assigned menu items, the language and the Default column. The toolbar offers Duplicate, Delete and Options, and the row actions open the edit form. System → Site Templates lists the installed templates instead (the extensions), and offers Details and Files (the file editor) and Create Child Template.
A style’s edit form has these tabs:
-
Advanced: the parameters the template declares in its
<config>element. The tab names come from thefieldsetnames of the manifest, so a template with a fieldsetadvancedshows an Advanced tab. -
Menu Assignment: a tree of every menu and its items, with a checkbox per item and a toggle per menu. Only non-default styles can be assigned here, because the default style applies to everything that is not assigned.
-
Permissions is a component-level matter handled in Options, not in the style form.
The fields above the tabs are Style Name, Default (a switch, or a content-language selector on a multilingual site) and the template name, which is read-only.
Four rules govern the buttons, and all of them come from StyleModel:
-
There is exactly one default style per client. Setting a style as default (
setHome()) first resets thehomecolumn of the client’s other rows. A style whose template is disabled cannot be made the default. -
The default style cannot be deleted, and its Default switch is read-only. Make another style the default first.
-
Duplicate copies a style with all its parameters and an incremented title ("Tallpine - Default (2)"), without menu assignments and never as the default.
-
On a multilingual site (see Multilingual sites) the Default field becomes a language selector, and each content language may have its own default style: the German pages of the club can use a style whose logo reads "Tallpine Wanderverein" while English pages use the first one.
homethen holds the language tag.
Assigning a style to menu items
The default style is used for every page that does not name another. There are two ways to name another, and the official guide says they "produce the same result":
-
From the style: open a non-default style and use its Menu Assignment tab. This is better for groups of items.
-
From the menu item: open the item (Menus → <menu> → <item>) and choose the style in its Template Style field, on the Details tab. This is better for a single item. See Menus and menu items.
Both write the style’s id to the menu item’s template_style_id. Assigning a style to a menu item affects only the
page of that item. Pages reached without a matching menu item (a component URL with no menu entry, an error page) fall
back to the default, which is why a "member area" look often needs the style assigned to every item of the member
menu rather than only to the top one.
The Tallpine club uses three styles:
| Style | Template | Used for |
|---|---|---|
Tallpine - Default |
|
Everything that is not assigned elsewhere (default) |
Tallpine - Trail Warden |
|
The items of the Trail Warden menu, with a darker header and the |
Tallpine - Print |
|
A hidden item used as the target of "print this trail" links |
A site builder can also give the home page a separate look without a second template: the User Guide describes
assigning a different, non-default style to the Home item only, or adding a Page Class to the Home item and styling
it in user.css.
How Joomla chooses the style for a request
The decision is made once per request by SiteApplication, whose template initialisation was refactored into
dedicated methods in Joomla 6.1. In order:
-
Load all styles of enabled site templates (a join of
#__template_stylesand#__extensionswithenabled = 1), cached in thecom_templatescache group. The default style, or on a multilingual site the default of the current language, is stored as element 0 of the list. -
Take the
template_style_idof the active menu item, or of the item named by theItemidrequest variable when there is no active item. -
If the request contains
templateStyle=<id>, that id wins over the menu item’s. -
Look the id up in the list; if it is not there (no style, a disabled template) use element 0, the default.
-
If the request contains
template=<name>and an enabled site template of that name with a usableindex.phpexists, that template’s style replaces the choice. -
If the chosen template has no
index.phpof its own and no parentindex.phpeither, an error message is queued ("could not find the template") and Joomla falls back to the cassiopeia style. Only when Cassiopeia is missing too is an exception thrown.
The two request variables are useful tools and small risks:
-
?templateStyle=14is handy for previewing an unpublished-looking style on a staging site, but it also means that any visitor can render any page with any style of an enabled template. Keep styles of template experiments on a staging site, not on production. -
?template=cassiopeiadoes the same by template name. Because it is honoured only for enabled templates it cannot load an arbitrary folder, and the name is filtered as a command-type string, but the result is still a second URL for the same content: a page cache or a CDN that keys on the full URL will store a copy, and a crawler that follows a link containing it will index it. Do not generate such links in content. -
The style is part of what the page cache stores. After changing the default style or an assignment, clear the cache (see Caching and performance); the style list itself is cached and saving a style cleans the
com_templatesgroup.
templateDetails.xml
templateDetails.xml is the manifest of a template: the installer reads it to know what to copy and register, and
Joomla reads it again at runtime to build the style form and to list the positions. The file name is fixed and the
file must sit in the template’s root folder.
The overall shape
<?xml version="1.0" encoding="UTF-8"?>
<extension type="template" client="site" method="upgrade">
<name>cassiopeia_tallpine</name>
<version>1.2.0</version>
<creationDate>2026-10</creationDate>
<author>Tallpine Hiking Club web team</author>
<authorEmail>web@tallpine.example</authorEmail>
<copyright>(C) 2026 Tallpine Hiking Club</copyright>
<license>GNU General Public License version 2 or later</license>
<description>TPL_CASSIOPEIA_TALLPINE_XML_DESCRIPTION</description>
<inheritable>0</inheritable>
<parent>cassiopeia</parent>
<namespace path="src">Tallpine\Template\CassiopeiaTallpine</namespace>
<files>
<filename>index.php</filename>
<filename>component.php</filename>
<filename>error.php</filename>
<filename>offline.php</filename>
<filename>joomla.asset.json</filename>
<filename>templateDetails.xml</filename>
<folder>html</folder>
<folder>src</folder>
</files>
<media destination="templates/site/cassiopeia_tallpine" folder="media">
<folder>css</folder>
<folder>js</folder>
<folder>scss</folder>
<folder>images</folder>
</media>
<positions>
<position>trail-conditions</position>
<position>partner-strip</position>
</positions>
<languages folder="language">
<language tag="en-GB">en-GB/tpl_cassiopeia_tallpine.ini</language>
<language tag="en-GB">en-GB/tpl_cassiopeia_tallpine.sys.ini</language>
<language tag="de-DE">de-DE/tpl_cassiopeia_tallpine.ini</language>
<language tag="de-DE">de-DE/tpl_cassiopeia_tallpine.sys.ini</language>
</languages>
<config>
<fields name="params">
<fieldset name="advanced">
<field
name="accentColour"
type="color"
label="TPL_CASSIOPEIA_TALLPINE_ACCENT_LABEL"
default="#2f6b3a"
/>
</fieldset>
</fields>
</config>
</extension>
Source: Joomla Manual: templateDetails.xml and Joomla CMS source: Cassiopeia Extended templateDetails.xml
The root <extension> element carries type="template" and the client. method="upgrade" lets a newer package be
installed over the older one without uninstalling it first, which would delete the styles; it is the form to use for
anything you will ship more than once. Everything below is explained element by element.
Metadata elements
<name> must equal the folder name: it becomes the element in #__extensions and the template in
#__template_styles. <version>, <creationDate>, <author>, <authorEmail>, <copyright> and <license> are
shown in the Site Templates list and the extension manager. <description> is usually a language constant
(TPL_..._XML_DESCRIPTION) defined in the .sys.ini file so that the installer can translate it. The core
templates declare creationDate as year and month ("2025-07") and version as "1.0": the version of a core template
is not the Joomla version, and a template that is distributed separately should use a version of its own and an update
server (see Update servers and the JED).
<files>
<files> lists what the installer copies into templates/<name>/ (or the administrator path), as <filename> and
<folder> children. Two things are easy to get wrong:
-
A file that exists in the package but is not listed is not installed. A forgotten
<filename>offline.php</filename>is the classic cause of the offline page falling back to the default one only on the live site while it works on the developer’s machine. -
<folder>html</folder>copies the whole folder, which is where template overrides live (see Template overrides and alternative layouts). Listing only the files you changed keeps a child template honest: the files it does not list are served from the parent. Core child templates, including Cassiopeia Extended, nevertheless list the four entry files.
templateDetails.xml itself is listed so that it remains in the installed folder; the style form reads it from there.
<media>
<media> is a different copy: its destination is a path relative to the site’s media/ folder, and folder names
the folder inside the package that holds the files. Stylesheets, scripts, images and fonts that browsers request go
here, so that they are served from media/templates/site/cassiopeia_tallpine/, a folder the web server can expose
without exposing templates/ PHP files. Cassiopeia uses destination="templates/site/cassiopeia"; an administrator
template uses templates/administrator/<name>. Asset URIs in joomla.asset.json are resolved relative to this folder
(see Web Asset Manager).
With the public-folder layout of Joomla 6 (see
Installation and the public folder) the split between PHP
files in templates/ and public files in media/ is what lets an installation keep the former outside the web root.
A template that loads its CSS from templates/<name>/css/ instead of media/ breaks on such a site.
<positions>
<positions> lists one <position> per slot name. The list feeds the Position drop-down of the module editor and
the Preview Module Positions outlines. It draws nothing: the matching jdoc:include in the entry file does. Two
lists must therefore agree, and the module-side effects are described in
Modules and positions. Positions used directly by code instead of a
modules include (Cassiopeia’s error-403 and error-404, Atum’s cpanel and icon) are listed anyway, with an XML
comment saying so, so that they can be chosen in the editor.
The positions of the parent template stay usable in a child template. Cassiopeia Extended repeats the parent’s full list, while the club’s child template declares only the two positions it adds. A position name is free text; the module editor lets you type one that no template declares, which then simply displays nothing until a template outputs it.
<languages>
<languages folder="language"> copies the INI files into the template language folder and registers them. The names
follow tpl_<name>.ini (strings of the template at runtime, including the labels of the <config> fields) and
tpl_<name>.sys.ini (strings used by the installer and the Administrator lists: the name and description). At runtime
HtmlDocument loads the parent template’s language file first and then the child’s, so a child needs to define only
the strings it adds or changes. See
Language files and internationalisation.
<config>: the style form
Everything under <config><fields name="params"> becomes the form of the style’s parameters. It is the same XML form
syntax used elsewhere in Joomla (see Forms and form fields): each
<fieldset> becomes a tab, each <field> a control, and the saved values land, as JSON, in the params column of
#__template_styles. name="params" is required; the entry files read the values with
$this->params->get('name', 'default').
The style form is built from the style’s own template’s manifest. A child template does not inherit the parent’s
<config>: Cassiopeia Extended repeats Cassiopeia’s fields and adds its own colour and font settings, and a child
that declares nothing gets a style with no template parameters at all. A child that wants the parent’s logo, colour
and layout switches must copy those <field> elements, or it must read the parent’s defaults in code.
The 5.1 release added an override for the Menu Assignment tab. A template can place a <fields name="assigned">
element in <config> with a field of type menus, and write the field class in its own namespace, to replace the
tab’s default tree with a custom one; the Developer Manual shows a sample MenusField that builds the list with
MenusHelper::getMenuLinks(). Most templates never need it.
<inheritable> and <parent>
These two elements, available since Joomla 4.1, express the child-template relationship:
-
<inheritable>1</inheritable>declares that the template may serve as a parent. Cassiopeia and Atum set it to1; a leaf template such as Cassiopeia Extended orcassiopeia_tallpinesets0. The Create Child Template button on Site Templates → <template> Details and Files is offered only for templates withinheritable1. The value is copied to theinheritablecolumn of each style. -
<parent>cassiopeia</parent>names the parent’s folder. It is copied to theparentcolumn of the styles, and from then on Joomla uses it in four places: file resolution (a missing entry file is taken from the parent), the asset registry (the parent’sjoomla.asset.jsonis registered before the child’s), language loading (the parent’s INI files first) and overrides (the parent’shtml/folder is searched after the child’s).
SiteApplication registers the registries in exactly that order:
// libraries/src/Application/SiteApplication.php (excerpt, simplified)
$template = $this->getTemplate(true);
$wr = $document->getWebAssetManager()->getRegistry();
if (!empty($template->parent)) {
$wr->addTemplateRegistryFile($template->parent, $this->getClientId());
}
$wr->addTemplateRegistryFile($template->template, $this->getClientId());
Only one level of inheritance is supported: a template can have one parent and that parent cannot itself be a child. The mechanics, the Create Child Template wizard and what to copy are in Child templates.
<namespace>
Since Joomla 4 an extension may declare a PSR-4 namespace. The Developer Manual’s template skeleton shows
<namespace path="src"> with a name of the form company, Templates, template name, and a src folder with field
classes and helpers (it is the home of the menus field class mentioned above). The namespace has to be unique, must
match the use statements in the template’s files, and the src folder has to be listed under <files>.
Other optional elements
A template manifest may also contain <help> (a key and url for the toolbar’s Help button in the style form,
which StyleModel reads), <updateservers> (see
Update servers and the JED) and <scriptfile> for an installer
script (see Installer scripts and SQL updates). Nothing in
<config> is evaluated except the form definition: do not try to put logic in the manifest.
The entry files
The template folder holds up to four PHP files that Joomla itself requires, each chosen by the situation rather than by a menu setting.
| File | Used when | $this is |
|---|---|---|
|
Every normal HTML page |
|
|
The URL has |
|
|
An uncaught exception ends up in the error handler (404, 403, 500) |
|
|
The site is offline and the visitor may not log in offline |
|
How Joomla finds the file to render
SiteApplication::render() decides the file name, then HtmlDocument::_fetchTemplate() locates it:
-
The file is
<tmpl>.php, wheretmplis the request variable and defaults toindex. -
If the site is offline and the user lacks the
core.login.offlinepermission, the file becomesoffline.phpand the response gets503 Service Temporarily Unavailable. -
If
tmpl=offlineis requested while the site is online,index.phpis used instead, so nobody can preview the offline page from the outside. -
For errors,
ErrorDocumentsets the file toerror.phpitself and sets the HTTP status to the exception’s code (500 when it is outside 400-599). -
_fetchTemplate()loads the file fromtemplates/<name>/. If it is not there it triestemplates/<parent>/, and as a last resorttemplates/system/(usingindex.phpthere when even the system folder lacks that file).
The practical consequence for a child template: you override an entry file simply by creating a file with the same
name in the child. The child copy replaces the parent file completely; there is no merging. That is why the
Tallpine index.php below repeats the parent’s asset setup instead of calling into it.
index.php
index.php is the page. It has three jobs: prepare the document (assets, metadata, parameters), output the HTML
skeleton, and place the jdoc:include elements. Joomla runs it with require, so $this is the HtmlDocument and
the PHP variables defined in it are plain local variables. The file must start with defined('_JEXEC') or die; (in
Joomla 6 JPATH_PLATFORM is no longer defined, so the older defined('JPATH_PLATFORM') guard no longer works unless
the Backward Compatibility 6 plugin is on).
The following is the Tallpine child template’s page. It keeps Cassiopeia’s asset presets and adds the two positions,
a colour parameter and a sticky notice for members. Run on Joomla 6.2 with cassiopeia_tallpine set as default style
it produces a complete page.
<?php
/**
* @package Tallpine.Site
* @subpackage Templates.cassiopeia_tallpine
*
* @copyright (C) 2026 Tallpine Hiking Club
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
/** @var Joomla\CMS\Document\HtmlDocument $this */
$app = Factory::getApplication();
$input = $app->getInput();
$wa = $this->getWebAssetManager();
$menu = $app->getMenu()->getActive();
$sitename = htmlspecialchars($app->get('sitename'), ENT_QUOTES, 'UTF-8');
$pageclass = $menu !== null ? $menu->getParams()->get('pageclass_sfx', '') : '';
// Assets of the parent (registered first) and of the child
$wa->usePreset('template.cassiopeia.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr'))
->useStyle('template.active.language')
->useStyle('template.user')
->useScript('template.user')
->useStyle('template.tallpine');
// Override 'template.active' so the language stylesheet depends on the right preset
$wa->registerStyle('template.active', '', [], [], ['template.cassiopeia.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr')]);
// Template style parameters, always with a default
$accent = $this->params->get('accentColour', '#2f6b3a');
$wa->addInlineStyle(':root { --tallpine-accent: ' . htmlspecialchars($accent, ENT_QUOTES, 'UTF-8') . '; }');
$this->setMetaData('viewport', 'width=device-width, initial-scale=1');
$hasLeft = $this->countModules('sidebar-left', true);
$hasRight = $this->countModules('sidebar-right', true);
?>
<!DOCTYPE html>
<html lang="<?php echo $this->language; ?>" dir="<?php echo $this->direction; ?>">
<head>
<jdoc:include type="metas" />
<jdoc:include type="styles" />
<jdoc:include type="scripts" />
</head>
<body class="site <?php echo $input->getCmd('option', '') . ' view-' . $input->getCmd('view', '') . ($pageclass ? ' ' . $pageclass : ''); ?>">
<header class="header container-header full-width">
<div class="grid-child">
<a class="brand-logo" href="<?php echo $this->baseurl; ?>/"><?php echo $sitename; ?></a>
</div>
<?php if ($this->countModules('menu', true)) : ?>
<div class="grid-child container-nav">
<jdoc:include type="modules" name="menu" style="none" />
</div>
<?php endif; ?>
</header>
<?php if ($this->countModules('trail-conditions', true)) : ?>
<aside class="container-trail-conditions" aria-label="<?php echo Text::_('TPL_CASSIOPEIA_TALLPINE_TRAIL_CONDITIONS'); ?>">
<jdoc:include type="modules" name="trail-conditions" style="none" />
</aside>
<?php endif; ?>
<div class="site-grid<?php echo $hasLeft ? ' has-sidebar-left' : ''; ?><?php echo $hasRight ? ' has-sidebar-right' : ''; ?>">
<?php if ($hasLeft) : ?>
<div class="grid-child container-sidebar-left">
<jdoc:include type="modules" name="sidebar-left" style="card" />
</div>
<?php endif; ?>
<div class="grid-child container-component">
<jdoc:include type="modules" name="breadcrumbs" style="none" />
<jdoc:include type="modules" name="main-top" style="card" />
<jdoc:include type="message" />
<main>
<jdoc:include type="component" />
</main>
<jdoc:include type="modules" name="main-bottom" style="card" />
</div>
<?php if ($hasRight) : ?>
<div class="grid-child container-sidebar-right">
<jdoc:include type="modules" name="sidebar-right" style="card" />
</div>
<?php endif; ?>
</div>
<?php if ($this->countModules('partner-strip', true)) : ?>
<div class="container-partner-strip full-width">
<jdoc:include type="modules" name="partner-strip" style="none" />
</div>
<?php endif; ?>
<?php if ($this->countModules('footer', true)) : ?>
<footer class="container-footer footer full-width">
<div class="grid-child">
<jdoc:include type="modules" name="footer" style="none" />
</div>
</footer>
<?php endif; ?>
<jdoc:include type="modules" name="debug" style="none" />
</body>
</html>
Points to notice, all of them taken from how Cassiopeia itself is written:
-
The three
jdoc:includeelements in<head>(metas,styles,scripts) are what output everything the Web Asset Manager and the document collected. Leaving one out removes its output silently: a template withouttype="scripts"loads no JavaScript at all, including the code of core features. -
type="message"prints the system messages (success notices, errors). Omit it and a failed login shows nothing. -
type="component"prints the main component output exactly once. It belongs inside a<main>landmark. -
countModules('name', true)counts modules that have output. Without the second argument a module with empty output still makes the wrapper appear. Several names may be combined, for examplecountModules('sidebar-left + sidebar-right'). -
The
styleattribute is the default module style (chrome) for the position. Positions that carry bordered cards in Cassiopeia usecard; the othersnone. See Layouts and module chromes. -
Everything printed from a parameter is escaped. The accent colour reaches an inline stylesheet, where an unescaped value could break out of the declaration.
-
The assets are named, not echoed.
template.tallpineis registered in the child’sjoomla.asset.json:
{
"$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json",
"name": "cassiopeia_tallpine",
"version": "1.2.0",
"description": "Assets of the Tallpine Hiking Club child template.",
"license": "GPL-2.0-or-later",
"assets": [
{
"name": "template.tallpine",
"description": "Stylesheet with the Tallpine colours, the trail-conditions banner and the partner strip.",
"type": "style",
"uri": "template-tallpine.css",
"dependencies": [
"template.cassiopeia.ltr"
]
}
]
}
Source: Joomla Manual: Web Asset Manager
The uri is resolved against media/templates/site/cassiopeia_tallpine/css/. The dependency on the parent’s preset
is what guarantees Cassiopeia’s CSS loads before the child’s overrides.
component.php
component.php is the stripped-down page used for tmpl=component URLs: the print view of an article, the
modal windows of the Administrator, an iframe target. It should contain metas, styles, scripts, message and
component, and nothing that depends on modules or on the site grid. Cassiopeia’s is only the document skeleton with
those five includes and the same asset setup as index.php. A child template can omit its own and inherit that one.
<?php
/**
* @package Tallpine.Site
* @subpackage Templates.cassiopeia_tallpine
*
* @copyright (C) 2026 Tallpine Hiking Club
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
/** @var Joomla\CMS\Document\HtmlDocument $this */
$wa = $this->getWebAssetManager();
$wa->usePreset('template.cassiopeia.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr'))
->useStyle('template.active.language')
->useStyle('template.user')
->useScript('template.user')
->useStyle('template.tallpine');
$wa->registerStyle('template.active', '', [], [], ['template.cassiopeia.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr')]);
?>
<!DOCTYPE html>
<html lang="<?php echo $this->language; ?>" dir="<?php echo $this->direction; ?>">
<head>
<jdoc:include type="metas" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<jdoc:include type="styles" />
<jdoc:include type="scripts" />
</head>
<body class="contentpane component <?php echo $this->direction === 'rtl' ? 'rtl' : ''; ?>">
<jdoc:include type="message" />
<jdoc:include type="component" />
</body>
</html>
The Tallpine site uses it for the "print this trail" link: the link adds tmpl=component and print=1 to the article
URL, and the page loses the header, the sidebars and the footer without any extra template.
error.php
When an exception reaches ExceptionHandler, Joomla builds an ErrorDocument and renders the template’s error.php
with $this->error (the Throwable) and $this->debug (true when system debug is on). The HTTP status is the
exception’s code. This is the page your visitors see for 404 and 403, so it deserves the template’s navigation.
The caution is in Cassiopeia’s own source: the module renderer does not work properly in an error page because the
application is not fully initialised, so it only renders modules when an identity and a language exist, and
it uses a variable named $renderModules to decide. The template also supports one module position per error code,
error-404, error-403, and so on: if the position error- plus the status code has modules, they are the page
content; otherwise the template prints a default explanation. That lets a site builder write the 404 text in a Custom
module without touching PHP. The positions are listed in the manifest only so that they appear in the position list.
<?php
/**
* @package Tallpine.Site
* @subpackage Templates.cassiopeia_tallpine
*
* @copyright (C) 2026 Tallpine Hiking Club
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
/** @var Joomla\CMS\Document\ErrorDocument $this */
$app = Factory::getApplication();
$wa = $this->getWebAssetManager();
$wa->usePreset('template.cassiopeia.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr'))
->useStyle('template.active.language')
->useStyle('template.tallpine');
$wa->registerStyle('template.active', '', [], [], ['template.cassiopeia.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr')]);
$errorCode = $this->error->getCode();
// Modules need an identity and a language; both can be missing on an early failure
$renderModules = $app->getIdentity() && $app->getLanguage();
?>
<!DOCTYPE html>
<html lang="<?php echo $this->language; ?>" dir="<?php echo $this->direction; ?>">
<head>
<jdoc:include type="metas" />
<jdoc:include type="styles" />
<jdoc:include type="scripts" />
</head>
<body class="site error_site">
<header class="header container-header full-width">
<a class="brand-logo" href="<?php echo $this->baseurl; ?>/">Tallpine Hiking Club</a>
<?php if ($renderModules && $this->countModules('menu')) : ?>
<jdoc:include type="modules" name="menu" style="none" />
<?php endif; ?>
</header>
<main class="container-component">
<?php if ($renderModules && $this->countModules('error-' . $errorCode)) : ?>
<jdoc:include type="message" />
<jdoc:include type="modules" name="error-<?php echo $errorCode; ?>" style="none" />
<?php else : ?>
<h1><?php echo Text::_('TPL_CASSIOPEIA_TALLPINE_ERROR_TITLE'); ?></h1>
<p><?php echo htmlspecialchars($this->error->getMessage(), ENT_QUOTES, 'UTF-8'); ?></p>
<p><a class="btn btn-secondary" href="<?php echo $this->baseurl; ?>/"><?php echo Text::_('JERROR_LAYOUT_HOME_PAGE'); ?></a></p>
<?php endif; ?>
<?php if ($this->debug) : ?>
<?php echo $this->renderBacktrace(); ?>
<?php endif; ?>
</main>
</body>
</html>
Security consequences of the error page:
-
The exception message and the backtrace are printed only when
$this->debugis true. Never output$this->error->getMessage()unescaped (an SQL error may contain query fragments, and a 404 message contains the requested path) and never print the backtrace on a production site; the Error Reporting option in the Global Configuration governs the debug flag as well as the HTTP status text. -
An error page is also rendered for a failing template. A fatal error inside
error.phpis the worst place to fail, so keep it simple and test it by requesting a nonexistent URL and by forcing an exception.
The complete treatment of the error, offline and component pages, including the HTTP semantics, is in Error, offline and component pages.
offline.php
When Site Offline is set in the Global Configuration, visitors without the core.login.offline permission (every
guest, and by default everyone except Super Users) see offline.php with a 503 status. It must therefore contain its
own login form, because the normal login module is not available. Cassiopeia’s version shows the offline image and
message (or the default JOFFLINE_MESSAGE), and a form that posts to com_users with the user.login task and a
CSRF token, plus the extra buttons that authentication plugins such as passkeys contribute. It loads the
template.offline asset from its registry.
<?php
/**
* @package Tallpine.Site
* @subpackage Templates.cassiopeia_tallpine
*
* @copyright (C) 2026 Tallpine Hiking Club
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
/** @var Joomla\CMS\Document\HtmlDocument $this */
$app = Factory::getApplication();
$wa = $this->getWebAssetManager();
$wa->usePreset('template.cassiopeia.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr'))
->useStyle('template.active.language')
->useStyle('template.offline')
->useStyle('template.tallpine');
$wa->registerStyle('template.active', '', [], [], ['template.cassiopeia.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr')]);
$message = $app->get('offline_message');
?>
<!DOCTYPE html>
<html lang="<?php echo $this->language; ?>" dir="<?php echo $this->direction; ?>">
<head>
<jdoc:include type="metas" />
<jdoc:include type="styles" />
<jdoc:include type="scripts" />
</head>
<body class="site offline">
<jdoc:include type="message" />
<main class="offline-card">
<h1><?php echo htmlspecialchars($app->get('sitename'), ENT_QUOTES, 'UTF-8'); ?></h1>
<p>
<?php echo $message !== '' && $message !== null
? htmlspecialchars($message, ENT_QUOTES, 'UTF-8')
: Text::_('JOFFLINE_MESSAGE'); ?>
</p>
<form action="<?php echo Route::_('index.php', true); ?>" method="post" id="form-login">
<label for="username"><?php echo Text::_('JGLOBAL_USERNAME'); ?></label>
<input name="username" id="username" type="text" autocomplete="username">
<label for="password"><?php echo Text::_('JGLOBAL_PASSWORD'); ?></label>
<input name="password" id="password" type="password" autocomplete="current-password">
<button type="submit" class="btn btn-primary"><?php echo Text::_('JLOGIN'); ?></button>
<input type="hidden" name="option" value="com_users">
<input type="hidden" name="task" value="user.login">
<input type="hidden" name="return" value="<?php echo base64_encode(Route::_('index.php', false)); ?>">
<?php echo HTMLHelper::_('form.token'); ?>
</form>
</main>
</body>
</html>
Pitfalls specific to this file: the offline page is rendered without the module positions of the normal page; the
offline message is administrator-supplied text that Cassiopeia prints unescaped (the safehtml filter has already
stripped dangerous markup when the configuration was saved); and because the form posts to the real login task,
a template author who removes the CSRF token (form.token) makes the site’s offline login fail with "invalid token".
The Global Configuration side of offline mode is in
Global configuration.
jdoc:include reference
The jdoc:include element is not HTML. It is a placeholder that HtmlDocument replaces after the template has run, by
calling a renderer chosen by the type attribute.
type |
Output | Attributes |
|---|---|---|
|
Charset, viewport, robots, description, generator, canonical and other |
none |
|
The stylesheets, inline styles and preloads the Web Asset Manager collected |
none |
|
The scripts, inline scripts and script options ( |
none |
|
The system message queue |
none |
|
The output of the component that handled the request |
none |
|
Every module published to a position |
|
|
A single module, found by its module type |
|
|
Legacy single element combining metas, styles and scripts |
none |
Source: Joomla Manual: Templates
Two cautions. First, jdoc:include elements are replaced by simple text substitution, so a style attribute that
names a chrome the template does not define falls back to the default chrome rather than failing, and a typo shows up
as unstyled modules rather than as an error. Second, the position attribute is a plain string: name="trail-conditions"
and name="trail_conditions" are two different positions.
Template code should avoid echoing <?php echo $this->getBuffer('modules', 'x'); ?> unless it needs the output as
a string; the countModules() check plus jdoc:include is the supported pattern and keeps the render order Joomla
expects.
Template parameters
Parameters make a template configurable by non-programmers and are the reason styles exist. They are defined in the manifest ([config: the style form]), stored per style, and read in the entry files.
Defining parameters
The following adds three fields to the Tallpine manifest: a colour, a switch that shows or hides a seasonal notice, and
a text field that is shown only when the switch is on. The showon attribute links the second field to the first, the
way Cassiopeia hides its logo and title fields when the brand switch is off.
<config>
<fields name="params">
<fieldset name="advanced" label="TPL_CASSIOPEIA_TALLPINE_FIELDSET_LABEL">
<field
name="accentColour"
type="color"
label="TPL_CASSIOPEIA_TALLPINE_ACCENT_LABEL"
default="#2f6b3a"
validate="color"
/>
<field
name="seasonNotice"
type="radio"
label="TPL_CASSIOPEIA_TALLPINE_SEASON_NOTICE_LABEL"
layout="joomla.form.field.radio.switcher"
default="0"
filter="integer"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field
name="seasonNoticeText"
type="text"
label="TPL_CASSIOPEIA_TALLPINE_SEASON_NOTICE_TEXT_LABEL"
default=""
filter="string"
showon="seasonNotice:1"
/>
<field
name="logoFile"
type="media"
schemes="http,https,ftp,ftps,data,file"
validate="url"
relative="true"
default=""
label="TPL_CASSIOPEIA_TALLPINE_LOGO_LABEL"
/>
</fieldset>
</fields>
</config>
The labels use constants defined in tpl_cassiopeia_tallpine.ini:
TPL_CASSIOPEIA_TALLPINE_FIELDSET_LABEL="Tallpine settings"
TPL_CASSIOPEIA_TALLPINE_ACCENT_LABEL="Accent colour"
TPL_CASSIOPEIA_TALLPINE_SEASON_NOTICE_LABEL="Show seasonal notice"
TPL_CASSIOPEIA_TALLPINE_SEASON_NOTICE_TEXT_LABEL="Notice text"
TPL_CASSIOPEIA_TALLPINE_LOGO_LABEL="Logo"
TPL_CASSIOPEIA_TALLPINE_TRAIL_CONDITIONS="Trail conditions"
TPL_CASSIOPEIA_TALLPINE_ERROR_TITLE="This trail does not exist"
Reading and using parameters
$this->params is a Joomla\Registry\Registry built from the style’s params JSON. Always pass a default, because
a style created before the field existed has no value for it, and never trust a stored value in markup:
<?php
// In index.php, after the asset setup
$seasonNotice = (int) $this->params->get('seasonNotice', 0);
$noticeText = trim((string) $this->params->get('seasonNoticeText', ''));
if ($seasonNotice && $noticeText !== '') {
echo '<div class="alert alert-info tallpine-season-notice" role="status">'
. htmlspecialchars($noticeText, ENT_QUOTES, 'UTF-8')
. '</div>';
}
// A logo selected in the media field is a relative path with an optional #joomlaImage suffix
$logo = $this->params->get('logoFile');
if ($logo) {
$logo = HTMLHelper::_('cleanImageURL', $logo)->url;
}
Notes on parameters:
-
The
filterattribute controls what is stored (integer,string,options,raw,unset…).validaterejects bad input before saving. Usefilter="integer"orvalidate="options"on switches and lists so a hand-edited POST cannot save an arbitrary value. Cassiopeia’s own fields use both. -
A
mediafield returns a path relative to the site root, to which Joomla may append#joomlaImage://...metadata. Clean it before use, as shown, and pass it throughhtmlspecialcharsif you print it. -
Raw HTML in a parameter (an "analytics snippet" field, for example) executes for every visitor and is an administrator-to-visitor XSS vector. Do not offer
filter="raw"fields to editors; use a Custom module with its own access control instead. -
Parameters belong to the style, not the template. Duplicating a style duplicates its parameters, and updating the template package does not overwrite saved values. When an update adds a field, the entry file must tolerate its absence.
-
A child template has its own parameters. Reading
$this->params->get('colorName')in the child only works if the child’s manifest declarescolorName(Cassiopeia’s<config>is not inherited). -
On the site, the active template’s parameters are also available as
$app->getTemplate(true)->params, which is how a module or a plugin reads them.
Inspecting styles with SQL
For a staging site, a quick query shows which style each client uses and what it stores:
SELECT id, template, client_id, home, title, parent, params
FROM `#__template_styles`
WHERE client_id = 0
ORDER BY home DESC, id;
Edit the table by hand only to recover a broken site (a default style pointing at a deleted template, for example, which sends every visitor to the Cassiopeia fallback with an error message). Prefer the Administrator, which also cleans the cache.
Previewing positions
A position is an invisible slot, so the fastest way to learn a template, or to find out why a module does not show, is to draw all of them.
-
Open System → Site Template Styles and click Options.
-
Set Preview Module Positions to Enabled and save. (The same switch exists under System → Site Templates → Options.) The setting is the
template_positions_displayparameter ofcom_templates, default Disabled. -
Visit the site with
?tp=1appended, or&tp=1when the URL already has a query string, for examplehttps://tallpine.example/?tp=1.
Each position the template outputs then appears outlined and labelled with its name and the module style in use. The
position list in the manifest is not enough by itself: a position declared in <positions> but not output by index.php
stays invisible, which is the quickest test for the two lists disagreeing. The same screen holds the other
Templates: Options: upload limit (default 10 MB), the allowed image, source and font extensions for the template file
editor and the way the file Compare view shows differences (side by side by default).
Two cautions. The switch is global, so while it is on any visitor can add ?tp=1 and see the layout and the names of
every module position, so turn it off after use and on production sites keep it off. And the outline style wraps real
modules too, so a page previewed with tp=1 is not a faithful rendering. The mechanics (a dummy module is prepended to
every empty position) and the interplay with module chromes are in
Modules and positions.
Common errors and pitfalls
-
"The module is published but I cannot see it." The template does not output its position, the position is spelled differently in the module and in
index.php, or the style assigned to the menu item uses a different template than the one you edited. Check the style first (the Template Style column of the menu item), then use?tp=1. -
A new
index.phphas no effect. A child template’s files are installed from the package: on a server the folder was not updated, the file was not listed under<files>, or the cache holds the old page. Clear the cache; look at the actual folder on disk. -
The default style cannot be deleted or unassigned. This is intended; make another style the default first.
-
All pages look the same after assigning a style to one menu item. The assignment is per item. Children of a menu item and component URLs without an item use the default.
-
Parameters disappear after an update. Updates keep the
paramscolumn; if a field’snamewas changed in the new manifest, the saved value is no longer read. Treat a field name as a stored key. -
The site shows "template not found" and falls back to Cassiopeia. The style points to a folder that no longer exists, or the folder lacks
index.phpand has no parent. Reinstall the template or repair the row. -
Styles and scripts missing. The head does not contain the three includes, or the asset is never
use`d. The page source shows nothing from the template’s CSS when `$wa->useStyle()is never called. -
Fatal error on a Joomla 6 site after copying an old template. Old templates call removed APIs (for example
JFactory,JTextandJHtmlaliases,JPATH_PLATFORMguards,$doc->addStyleSheet(), jQuery assumptions). Core keeps the legacy class aliases only through the Backward Compatibility 6 plugin. Move to the namespaced classes and the Web Asset Manager. -
tmpl=componentshows a styled page that is too large. The template’scomponent.phpis missing, so the parent’s ortemplates/system's is used; add a lean one. -
Offline login fails. The CSRF token or the hidden
optionandtaskfields are missing fromoffline.php. -
Wrong HTML language. Printing a hard-coded
lang="en"on a multilingual site; use$this->languageand$this->direction.
Security considerations
-
Restrict Create, Edit and Delete on
com_templatesand the file editor (Details and Files and Customise). The file editor writes PHP intotemplates/: a stolen editor account becomes remote code execution. Many production sites remove the permission entirely and deploy template files from version control, and the hardening guidance of Security hardening, MFA and passkeys recommends it. -
Every
defined('_JEXEC') or die;guard prevents the file from being executed directly by URL. Keep it in every PHP file of the template, including layout overrides. -
Escape everything echoed from
$this->params, from the request and from the error object. Template parameters are edited by administrators, but a lower-privileged account that can edit a style (the permission is separate from Super User) can still inject script into every page if the template prints parameters raw. -
Do not leave debugging in a template:
var_dump, the backtrace of the error page and the preview switch are information leaks. -
Third-party templates run with the same rights as core. Install only from the JED or from a vendor you can audit and keep them updated; see Template frameworks and page builders.
-
?template=and?templateStyle=are request-controlled, as described in How Joomla chooses the style for a request.
Accessibility of the generated markup is the responsibility of the template: the landmarks (header, nav, main,
footer), the order of positions, visible focus and the colour contrast of the accent parameter. The general rules are
in Accessibility, and the Joomla-specific colour modes and right-to-left support in
Colour modes, RTL and accessibility.
Version notes
-
Joomla 4.0: Cassiopeia and Atum replace Beez3, Protostar and Hathor; templates use
joomla.asset.jsonand the Web Asset Manager instead ofaddStyleSheet()andJHtml::_('jquery.framework'). -
Joomla 4.1: child templates, with the
<inheritable>and<parent>manifest elements and the matchinginheritableandparentcolumns of#__template_styles. -
Joomla 5.0: the ES5 assets were removed from core, so template scripts must be written for modern browsers.
-
Joomla 5.1: an override for the Menu Assignment tab through a
menusfield in<config>. -
Joomla 6.0: the
cassiopeia_extendedchild template ships in core;JPATH_PLATFORMis no longer defined and the legacyJ*aliases move to the Backward Compatibility 6 plugin. -
Joomla 6.1: the template initialisation of
SiteApplicationis split intoinitialiseTemplate(),getTemplates()and related methods (the order described above); a"version": "nocache"asset option arrives (see Web Asset Manager). -
Joomla 6.2: current; no change to the template manifest or entry files.
Corrections to older books and tutorials
Older books, such as Joomla! 4 Masterclass (2023) and Developing Extensions for Joomla! 5 (2023), and many tutorials describe the same ideas as this page but need the following corrections for a 2026 site:
-
Child templates are described as "brand new". They have existed since Joomla 4.1, and since 6.0 core itself ships one (
cassiopeia_extended); treat them as the standard way to customise Cassiopeia rather than an experiment. -
Template examples built on
JFactory,JHtml,JTextor$doc->addScript()and jQuery need rewriting with the namespaced classes and the Web Asset Manager; the aliases are no longer defined on a new Joomla 6 install. -
Links to
docs.joomla.org/J4.xpages andlaunch.joomla.orgare dead or obsolete. The current documentation is the Joomla User Guide atguide.joomla.organd the Developer Manual atmanual.joomla.org. -
Joomla 4 is end of life (October 2025) and 5.x receives security fixes only; the examples there are for versions that are no longer the target of a new project.
-
Several pages of the Manual about templates (the generic "Template" page, colour schemes) are still unfinished; the
templates/cassiopeiasource tree is the authoritative example, which is why this page cites it. -
The manual and older guides describe sixteen Cassiopeia positions; the manifest also lists
error-403anderror-404, used byerror.php.