Joomla Child Templates

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 child template is a small template that names another template as its parent and reuses everything it does not replace. Instead of copying Cassiopeia and carrying the copy forward through every Joomla update, you create a folder holding only the files that differ: a stylesheet, a handful of overrides, a language file, perhaps an index.php. Everything else is read from the parent at request time. This page explains how to create a child template in the Templates manager and by hand, what the two manifest elements parent and inheritable do, what a child can add, exactly how Joomla resolves a file between child and parent (templates, overrides, layouts, assets, language files and media), how a child differs from a copied template, and it builds the Tallpine Hiking Club’s cassiopeia_tallpine child template step by step. 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). Child templates exist since Joomla 4.1, so everything here also applies to Joomla 5.4 (security fixes only); where 5.4 differs, the text says so. Joomla 4.x and 3.x are end of life.

The audience is the themer and the site builder. You do not need to write PHP to make a child template that changes colours and fonts, but you do need it for new layouts. The accessibility checks that apply to any template are in Web accessibility and Colour modes, RTL and accessibility; they are not repeated here. The theory of template styles is in Templates and template styles, overrides are covered in depth in Template overrides and alternative layouts, and a template that inherits from nothing is built in Building a template from scratch.

Why child templates exist

Cassiopeia is the default site template. Its files live in templates/cassiopeia and media/templates/site/cassiopeia and are replaced by every Joomla update, so any edit you make there disappears at the next minor release. Before Joomla 4.1 the usual workaround was to copy the whole template, rename it and edit the copy. That is a copied template: it works, but it freezes the parent’s markup, CSS and JavaScript at the moment of the copy. Security fixes and accessibility improvements that Joomla later makes to Cassiopeia never reach the copy, and merging them by hand gets harder every release.

Joomla 4.1 added child templates to the Templates manager (the method is marked @since 4.1.0 in the source). A child:

  • keeps its own folder, so core updates never touch it;

  • needs only the files that differ, because a missing file is read from the parent;

  • appears as a normal template in the Administrator, with its own styles, parameters and menu assignments;

  • keeps benefiting from fixes in the parent for every file it does not override.

Joomla 6.0 added a second core template, cassiopeia_extended, which is itself a child of Cassiopeia and adds colour and typography settings. It is a useful reference implementation (see Cassiopeia and Cassiopeia Extended). Material written for Joomla 4.0 or 4.1 that calls child templates a brand-new feature should be read with that in mind: they are now the standard, documented way to customise Cassiopeia, and a core template relies on the same mechanism. Older books and tutorials, such as Joomla! 4 Masterclass (2023), describe the feature as brand new and link to the legacy wiki; the current end-user documentation is the User Guide at guide.joomla.org, and the Developer Manual at manual.joomla.org documents the manifest, although some of its template pages are still short stubs, so this page also cites the source tree.

The two manifest elements: parent and inheritable

A template is described by templateDetails.xml. Two elements make inheritance work:

Element Where Meaning

<inheritable>

In a template that may be used as a parent

1 marks the template as a parent candidate. Cassiopeia declares <inheritable>1</inheritable>. The Templates manager offers the Create Child Template button only for such templates. A child declares <inheritable>0</inheritable>.

<parent>

In the child

The element name (folder name) of the parent, for example <parent>cassiopeia</parent>. Its presence is what turns the template into a child.

Joomla stores the same two values in the #__template_styles table (columns inheritable and parent), so every template style knows its parent. At request time SiteApplication::getTemplate(true) returns an object carrying template, inheritable and parent, and that object is what the document, the layout system, the module helper and the asset registry consult. The rule that results is simple:

  • A template with inheritable set, or with a parent, is a "modern" template: its static files are read from media/templates/site/NAME rather than from templates/NAME. Templates written for Joomla 3 keep their CSS inside the template folder; Cassiopeia and every child do not.

  • There is a single level of inheritance. A child names one parent, and a child is never itself inheritable. When you press Create Child Template on a template that already has a parent, the new template names the original parent, not the intermediate one.

The name of a child created in the Templates manager is always PARENT_NAME. If the parent is cassiopeia and you type tallpine, the new element is cassiopeia_tallpine. The name you type may contain only letters, digits and underscores; anything else is rejected with an error. That is why the Tallpine child is cassiopeia_tallpine and not cassiopeia-tallpine.

Creating a child template in the Templates manager

The Templates manager creates the folder, the manifest and the media structure for you. The steps follow the User Guide:

  1. Open System, then Site Templates under the Templates panel.

  2. Select Cassiopeia Details and Files.

  3. Choose Create Child Template in the toolbar.

  4. Type the name. For the club, tallpine. Leave Additional Template Styles empty unless you want to copy styles of the parent into the child (see below).

  5. Save, close the parent form, and open the new entry cassiopeia_tallpine Details and Files from the list.

Your account needs the core.create permission on the Templates component; without it the button reports "not permitted". Internally the controller does the following (TemplateController::child() and TemplateModel::child() in com_templates):

  1. validates the name and checks it is not already taken;

  2. builds the new template in the temporary folder (the tmp_path of the Global Configuration), under a name starting with template_child_;

  3. copies the parent’s templateDetails.xml there and rewrites it (see the table below);

  4. creates empty html and media folders, the latter with css, js, images and scss subfolders;

  5. runs the normal installer with installtype set to folder, which copies the files into templates/NAME and media/templates/site/NAME and registers the template as an extension, creating one default style named "NAME - Default".

What the rewritten manifest keeps and removes:

Manifest part What happens to it

<name>

Set to PARENT_newname, in lower case.

<creationDate>, <author>, <authorEmail>

Replaced by the current month and the creating user.

<files>

Replaced by templateDetails.xml and the html folder only. Every other file of the parent is dropped, which is the point of a child.

<media>

Replaced by a media block whose destination is templates/site/NAME, listing css, js, images and scss.

<languages>, <update>, <updateservers>

Removed. A new child has no language files and no update server.

<inheritable> and <parent>

inheritable is set to 0 and parent to the parent’s element name.

<namespace>

If present, _Newname is appended.

<positions>, <config>, <description>, <version>

Copied exactly from the parent when the child is created. The child therefore starts with the parent’s positions and with the parent’s style parameter form, but from then on its own manifest is the only one the module editor reads.

Additional Template Styles is a list of the parent’s existing template styles. Each one you tick is copied into the child as a new style, with the parent’s parameters, named from the language string COM_TEMPLATES_COPY_CHILD_TEMPLATE_STYLES. It saves recreating the logo, colour and font choices of a style that is already tuned. Menu assignments are not copied; assign the new style to menu items or make it the default yourself.

Right after creation the child contains one file, templateDetails.xml, an empty html folder and the empty media structure. Nothing is displayed differently yet: with no index.php, no stylesheet and no override of its own, the child renders exactly like Cassiopeia. That is the intended starting point.

Creating a child template by hand

The Templates manager is convenient on a production site, but a themer usually works in version control and builds an installable package. Write the folder yourself and zip it. For the Tallpine child, the repository contains:

cassiopeia_tallpine/
  templateDetails.xml
  index.php
  joomla.asset.json
  html/
    layouts/
      com_fields/
        field/
          render.php
    mod_custom/
      default.php
  language/
    en-GB/
      tpl_cassiopeia_tallpine.ini
      tpl_cassiopeia_tallpine.sys.ini
    de-DE/
      tpl_cassiopeia_tallpine.ini
      tpl_cassiopeia_tallpine.sys.ini
  media/
    css/
      tallpine.css
      user.css
      global/
        custom_tallpine.css
    js/
      tallpine.js
    images/
      tallpine-mark.svg

The manifest is the only file that is strictly required. It is the same kind of file the Templates manager produces, with the pieces a hand-built child usually adds:

<?xml version="1.0" encoding="UTF-8"?>
<extension type="template" client="site" method="upgrade">
    <name>cassiopeia_tallpine</name>
    <version>1.0.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>

    <!-- a child is not itself a parent -->
    <inheritable>0</inheritable>
    <parent>cassiopeia</parent>

    <files>
        <filename>index.php</filename>
        <filename>joomla.asset.json</filename>
        <filename>templateDetails.xml</filename>
        <folder>html</folder>
    </files>

    <!-- installed to media/templates/site/cassiopeia_tallpine -->
    <media destination="templates/site/cassiopeia_tallpine" folder="media">
        <folder>css</folder>
        <folder>js</folder>
        <folder>images</folder>
        <folder>scss</folder>
    </media>

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

    <!-- the module editor reads positions from this manifest only: list every position the template offers -->
    <positions>
        <position>topbar</position>
        <position>below-top</position>
        <position>menu</position>
        <position>search</position>
        <position>banner</position>
        <position>top-a</position>
        <position>top-b</position>
        <position>main-top</position>
        <position>main-bottom</position>
        <position>breadcrumbs</position>
        <position>sidebar-left</position>
        <position>sidebar-right</position>
        <position>bottom-a</position>
        <position>bottom-b</position>
        <position>footer</position>
        <position>debug</position>
        <position>error-403</position>
        <position>error-404</position>
        <position>trail-conditions</position>
        <position>partner-strip</position>
    </positions>

    <config>
        <fields name="params">
            <fieldset name="advanced">
                <field
                    name="brand"
                    type="radio"
                    label="TPL_CASSIOPEIA_BRAND_LABEL"
                    default="1"
                    layout="joomla.form.field.radio.switcher"
                    filter="options"
                    >
                    <option value="0">JNO</option>
                    <option value="1">JYES</option>
                </field>
                <field
                    name="logoFile"
                    type="media"
                    schemes="http,https,ftp,ftps,data,file"
                    validate="url"
                    relative="true"
                    default=""
                    label="TPL_CASSIOPEIA_LOGO_LABEL"
                    showon="brand:1"
                />
                <field
                    name="siteTitle"
                    type="text"
                    default=""
                    label="TPL_CASSIOPEIA_TITLE"
                    filter="string"
                    showon="brand:1"
                />
                <field
                    name="trailAlertEnabled"
                    type="radio"
                    layout="joomla.form.field.radio.switcher"
                    default="1"
                    label="TPL_CASSIOPEIA_TALLPINE_TRAIL_ALERT_LABEL"
                    description="TPL_CASSIOPEIA_TALLPINE_TRAIL_ALERT_DESC"
                    filter="options"
                    >
                    <option value="0">JNO</option>
                    <option value="1">JYES</option>
                </field>
            </fieldset>
        </fields>
    </config>
</extension>

The manifest above is shortened for reading: a production file also repeats the parent’s other parameters (colour theme, fonts, layout width, sticky header, back-to-top link) so that the style form keeps offering them. This matters and is explained in the section on parameters below.

Install the zipped folder with System, Install, Extensions, or copy it into templates/ and use Install, Discover. Either way Joomla registers the template, copies media/ to media/templates/site/cassiopeia_tallpine, copies the language files, and creates a default style. For repeatable local work, keep the working copy outside the container and mount the two live folders into the Joomla image from the Tallpine local environment described in Local development with Docker:

# compose.override.yaml: mount the child template into the running Joomla container
services:
  joomla:
    volumes:
      - ./cassiopeia_tallpine:/var/www/html/templates/cassiopeia_tallpine
      - ./cassiopeia_tallpine/media:/var/www/html/media/templates/site/cassiopeia_tallpine

With the bind mounts the template is not "installed" through the installer, so register it once from the Administrator with Install, Discover. Edits to the mounted files appear on the next request; the second mount is what makes the CSS, JavaScript and images visible at their public URL.

The lookup order

This is the central idea of the page. The figure shows where Joomla looks for each kind of file when the active template is cassiopeia_tallpine, whose parent is cassiopeia.

Lookup order for cassiopeia_tallpine: child first, then parent, then the core fallback, for template files, overrides, layouts and assets

The rules, taken from the 6.2 source:

  1. Template entry files (index.php, component.php, error.php, offline.php). HtmlDocument::_fetchTemplate() tries templates/CHILD/FILE, then templates/PARENT/FILE, then templates/system/FILE. The first file that exists is rendered. A child without an index.php therefore renders the parent’s.

  2. Component view overrides (html/com_content/article/default.php). HtmlView adds the parent’s override folder first and the child’s last, and the search takes the last-added path first. The child wins; then the parent; then the component’s own tmpl folder.

  3. Module overrides (html/mod_custom/default.php). ModuleHelper::getLayoutPath() checks the child’s html folder, then the parent’s, then the module’s tmpl folder.

  4. Layout overrides (html/layouts/..., used by LayoutHelper and FileLayout). FileLayout builds its list of include paths with the child first and the parent right after it, both for the component-specific overrides and for the general layouts, and the core layouts folder last.

  5. Relative asset files (css/user.css, js/tallpine.js). HTMLHelper::includeRelativeFiles() looks in media/templates/site/CHILD/css, then media/templates/site/PARENT/css. The same applies to js and images.

  6. Language files. Joomla loads tpl_PARENT first and tpl_CHILD second, so the child’s strings replace the parent’s strings of the same key.

  7. Asset registry files. SiteApplication adds templates/PARENT/joomla.asset.json before templates/CHILD/joomla.asset.json. When both define an asset with the same name, the later definition, the child’s, replaces the earlier one.

Two consequences follow. First, a child can shadow any single file of the parent by using the same relative path, which is how an override works. Second, a child cannot remove a file of the parent; it can only replace it, for example with an empty stylesheet of the same name.

What a child template can add

Module positions

Positions are listed in the manifest’s <positions> element, and the module editor reads them from the manifest of the active template only. A child’s manifest must therefore list every position it wants offered, including the parent’s. A child made in the Templates manager gets the copy automatically; a hand-written one must repeat them, as the manifest above does. Position names are shown through language keys of the form TPL_TEMPLATENAME_POSITION_POSITIONNAME in the template’s .sys.ini, so the Tallpine file defines TPL_CASSIOPEIA_TALLPINE_POSITION_TRAIL-CONDITIONS. Without a key, Joomla shows the raw name or a humanised version of it.

Declaring a position only makes it selectable. Nothing is drawn until some index.php contains a <jdoc:include type="modules" name="trail-conditions" /> line for it, so adding a position also means the child supplies its own index.php. The section on building cassiopeia_tallpine below shows a way to do that without copying Cassiopeia’s whole file. How positions and module styles work for editors is in Modules and positions.

Template style parameters

The <config> element defines the form shown under System, Site Template Styles, in the Advanced tab. The values are saved as JSON in the params column of #__template_styles and reach the template as $this->params inside index.php. Because the manifest is the one in the child folder, the child’s form is exactly what the child declares: if you add one field and delete the rest, the editor shows one field, and the parent’s index.php, still reading colorName, logoFile or fluidContainer, finds them empty and falls back to its defaults. Keep the parent’s fields and add your own after them. Use language keys with the child’s prefix for new labels. Reading a parameter in the child’s index.php always needs an escape on output:

$alert = (int) $this->params->get('trailAlertEnabled', 1);
$title = htmlspecialchars((string) $this->params->get('siteTitle', ''), ENT_QUOTES, 'UTF-8');

Cassiopeia’s colour field (colorName) is a filelist that points to media/templates/site/cassiopeia/css/global/ and lists files whose names start with custom. A child that wants its own colour schemes changes that field’s directory attribute to media/templates/site/cassiopeia_tallpine/css/global/ and drops custom_tallpine.css there. The parent’s index.php then registers global/custom_tallpine.css through the relative-file lookup above, finding it in the child’s media folder first.

CSS and JavaScript

There are two ways to add front-end code, and a child can use both:

  • user.css and user.js. Cassiopeia registers two assets, template.user for a style (file user.css) and template.user for a script (file user.js), and loads them after everything else: the style has weight 500 and depends on template.active. Create css/user.css or js/user.js in the child’s media folder and, because of the lookup order, they are used in place of the parent’s. If neither the child nor the parent has the file, nothing is loaded. This is what the User Guide calls the simplest customisation: for the child template the file goes to media/templates/site/cassiopeia_tallpine/css/user.css, which you can create and edit in the file manager of the Templates manager.

  • Your own registry file. A child with its own joomla.asset.json declares named assets with versions and dependencies, and uses them with useStyle() and useScript(). This is the right choice for a stylesheet that is more than a few overrides, for JavaScript that needs core, and whenever you want cache busting. Registry files are covered in Web Asset Manager.

Overrides

The child’s html folder holds overrides of extension output: html/com_content/article/default.php, html/mod_custom/default.php, html/layouts/joomla/.... The Templates manager’s Create Overrides tab copies the original file into the right place of the currently selected template, so select the child first. An override only needs to exist in the child when it differs; for every other view Joomla finds the parent’s override (Cassiopeia ships several, such as the mod_menu and mod_login layouts) or the extension’s original. The full theory, with alternative layouts and layout overrides, is in Template overrides and alternative layouts; custom fields are overridden in the way shown in Custom fields.

Language files

A child template can ship its own tpl_cassiopeia_tallpine.ini and .sys.ini for its parameter labels and position names, as the manifest’s <languages> block declares. The installer copies them to the global language/TAG folder; a language file that is instead left in templates/cassiopeia_tallpine/language/TAG is found by the fallback described next. The loading order, from HtmlDocument::_fetchTemplate(), is: the parent’s strings, then the child’s. Because the child is loaded second, you can change a parent label by repeating its key, for example TPL_CASSIOPEIA_BRAND_LABEL="Club logo bar", without editing the parent. Language-wide concerns, such as the German translation of the site, are in Language files and internationalisation and Multilingual sites.

Media files

All static files of a child live in media/templates/site/cassiopeia_tallpine, not in templates/cassiopeia_tallpine. Installation copies the package’s media/ folder there because of the <media destination=... folder="media"> element. Two pitfalls follow:

  • The media folder inside the package is only an install source. Edits made later in the Administrator’s file editor go to the live copy under media/templates/site/...; reinstalling the package overwrites them. Keep the package as the source of truth and rebuild it.

  • The live media folder must be writable by the web server user if you want to edit CSS in the Templates manager; the manager warns when the directory is not writable.

In a site that uses the public folder layout (see Installation and the public folder), the media folder is the one inside the public web root, which the source resolves through the JPATH_PUBLIC constant.

Child template versus copied template

Aspect Child template Copied template

What you maintain

Only the differing files

The whole template

Parent updates (security, accessibility, bug fixes)

Reach every file you did not override

Never arrive; merge by hand

Update safety of your folder

Own folder, never touched by Joomla updates

Own folder, but frozen

Replacing one file

Same relative path in the child

Edit the copy

Removing a parent file

Not possible; replace it

Delete it

Risk

An overridden file drifts away from the parent’s newer version

The whole template drifts

Manifest

<parent> and <inheritable>0</inheritable>

Ordinary template, no parent

Extra module positions and parameters

Declared in the child’s manifest

Declared in the copy

A copied template is still right when the design departs so far from Cassiopeia that little would be inherited, or when you want an independent codebase; in that case build from scratch (see Building a template from scratch) rather than copying. The Templates manager can also copy a template (Copy Template); copying a child there keeps the name rule above, so the copy is named PARENT_newname. For the Tallpine site the child wins: the club needs a colour scheme, two extra positions and three overrides, and wants Joomla’s accessibility fixes in Cassiopeia to keep arriving.

One drift remains with a child. An override copies markup at the moment you create it. When a later release changes the original, the child’s copy keeps the old markup. After each minor update, compare the overrides of cassiopeia_tallpine against the originals and merge what you need. The same is true of a child index.php that copies the parent’s, which is why the next section avoids copying it.

Building cassiopeia_tallpine

The Tallpine Hiking Club wants Cassiopeia with the club’s green and a warm accent, a coloured trail-conditions notice at the top of every page, a partner strip with the shops' banners above the page’s debug position, a distance badge in the custom fields list, and a partner strip that is announced to assistive technology. The steps use the files in the folder tree shown earlier.

Step 1: create the skeleton

Create the child in the Templates manager with the name tallpine, or write the manifest above and install the package. Check, after creation, that these exist:

ls templates/cassiopeia_tallpine
ls media/templates/site/cassiopeia_tallpine

Open System, Site Template Styles, find cassiopeia_tallpine - Default, make it the default for the site (the star), and rename it "Tallpine". The menu items that should use the club’s design inherit the default style; the ones that must keep another look can be assigned a style in their Template Style field.

Step 2: styles

First the colour scheme and typography, in user.css. Set CSS custom properties on :root, which Cassiopeia’s own stylesheet reads, so that components inherit them. Check contrast for every colour pair against the guidance in Colour modes, RTL and accessibility.

/* media/templates/site/cassiopeia_tallpine/css/user.css */
:root {
  --tallpine-green: #1f5e3a;
  --tallpine-green-dark: #144028;
  --tallpine-accent: #b5541c;
  --tallpine-paper: #f6f4ee;
  --link-color: var(--tallpine-green);
  --link-hover-color: var(--tallpine-green-dark);
}

.container-header {
  background-color: var(--tallpine-green);
  background-image: none;
}

.container-header .navbar-brand,
.container-header a {
  color: #ffffff;
}

.container-header a:focus-visible {
  outline: 3px solid #ffffff;
  outline-offset: 2px;
}

.btn-primary {
  background-color: var(--tallpine-accent);
  border-color: var(--tallpine-accent);
}

Load fonts from the site itself. Place the files in the child’s css/global or images folder and reference them with a relative @font-face; this avoids sending visitors' addresses to a third-party font host. A user stylesheet can also override Cassiopeia’s design tokens (the --cassiopeia-font-family-body and --cassiopeia-font-family-headings properties are the ones its font scheme sets).

For a larger stylesheet use a registry file. The child’s joomla.asset.json below registers the stylesheet template.tallpine with weight 400 (before user.css, which is 500) and a script with a version string, and also redefines template.user so that user.css is guaranteed to follow the club’s stylesheet:

{
  "$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json",
  "name": "cassiopeia_tallpine",
  "version": "1.0.0",
  "description": "Assets of the Tallpine Hiking Club child template.",
  "license": "GPL-2.0-or-later",
  "assets": [
    {
      "name": "template.tallpine",
      "description": "Club layout rules: trail notice, partner strip, badges.",
      "type": "style",
      "uri": "tallpine.css",
      "weight": 400,
      "dependencies": [
        "template.active"
      ]
    },
    {
      "name": "template.tallpine",
      "description": "Dismissable trail notice, remembers the choice for the session.",
      "type": "script",
      "uri": "tallpine.js",
      "version": "1.0.0",
      "attributes": {
        "defer": true
      },
      "dependencies": [
        "core"
      ]
    },
    {
      "name": "template.user",
      "description": "Load the club stylesheet before the user stylesheet.",
      "type": "style",
      "uri": "user.css",
      "weight": 500,
      "dependencies": [
        "template.active",
        "template.active.language",
        "template.tallpine"
      ]
    }
  ]
}

Because the parent’s registry is read first, redefining template.user is a legitimate use of the "later definition wins" rule. Do not redefine a parent asset unless you need to, because you then own every property of it; Joomla’s parent file also lists its template.user asset with the dependencies template.active and template.active.language, and the redefinition above repeats them. The uri values are relative file names, resolved by the lookup order against the child’s media folder and then the parent’s. Joomla 6.1 also accepts "version": "nocache", which disables browser caching of an asset during development; do not leave it in a release. Joomla 5.0 removed the ES5 and nomodule variants, so a script needs no legacy twin.

Step 3: an index.php that wraps the parent’s

The parent’s index.php is a few hundred lines. Copying it to add two positions would defeat the purpose of the child. A better pattern, which Cassiopeia Extended also uses in spirit when it includes the parent’s file, is to include the parent’s index.php and adjust its output:

<?php

/**
 * Template entry point of cassiopeia_tallpine.
 *
 * @package     Joomla.Site
 * @subpackage  Templates.cassiopeia_tallpine
 */

defined('_JEXEC') or die;

/** @var Joomla\CMS\Document\HtmlDocument $this */

$wa = $this->getWebAssetManager();

// Child assets declared in joomla.asset.json
$wa->useStyle('template.tallpine')
    ->useScript('template.tallpine');

// Let Cassiopeia render the whole page into a buffer
ob_start();
require JPATH_THEMES . '/cassiopeia/index.php';
$page = ob_get_clean();

// The jdoc tags are still plain text here; Joomla replaces them after this file returns.
if ((int) $this->params->get('trailAlertEnabled', 1) === 1 && $this->countModules('trail-conditions', true)) {
    $notice = '<div class="container-trail-conditions" role="region" aria-label="'
        . htmlspecialchars($this->getTitle(), ENT_QUOTES, 'UTF-8') . '">'
        . '<jdoc:include type="modules" name="trail-conditions" style="none" />'
        . '</div>';

    // put the notice just above the first <main> element
    $page = preg_replace('#<main\b#', $notice . '<main', $page, 1);
}

if ($this->countModules('partner-strip', true)) {
    $strip = '<div class="container-partner-strip">'
        . '<jdoc:include type="modules" name="partner-strip" style="none" />'
        . '</div>';

    // put the strip before the debug position at the end of the body
    $page = str_replace(
        '<jdoc:include type="modules" name="debug"',
        $strip . '<jdoc:include type="modules" name="debug"',
        $page
    );
}

echo $page;

The trade-off is that the script depends on two fragments of Cassiopeia’s markup, the <main tag and the debug include, so check it after every Joomla minor update. If the markup changes and the fragments are not found, the preg_replace() and str_replace() calls leave the page unchanged and the new positions simply do not appear, with no error. The alternative, copying Cassiopeia’s whole index.php and editing it, gives full control and a permanent merge burden. The CSS for the two positions goes in tallpine.css. Parameters used in the template (trailAlertEnabled) come from the child’s manifest, and the label for the module position comes from the .sys.ini file:

; language/en-GB/tpl_cassiopeia_tallpine.sys.ini
TPL_CASSIOPEIA_TALLPINE="Tallpine (Cassiopeia child)"
TPL_CASSIOPEIA_TALLPINE_XML_DESCRIPTION="Child template of Cassiopeia for the Tallpine Hiking Club."
TPL_CASSIOPEIA_TALLPINE_POSITION_TRAIL-CONDITIONS="Trail conditions notice"
TPL_CASSIOPEIA_TALLPINE_POSITION_PARTNER-STRIP="Partner shops strip"

; and in tpl_cassiopeia_tallpine.ini
TPL_CASSIOPEIA_TALLPINE_TRAIL_ALERT_LABEL="Show trail-conditions notice"
TPL_CASSIOPEIA_TALLPINE_TRAIL_ALERT_DESC="Shows the modules of the trail-conditions position above the main content."
TPL_CASSIOPEIA_BRAND_LABEL="Club logo bar"

The German files carry the same keys with German values. Because HtmlDocument loads the parent’s tpl_cassiopeia file and then the child’s, the last line above also changes the parent’s Brand label without touching Cassiopeia.

Step 4: overrides

Two overrides cover the club’s needs. The first adds a role and an accessible name to the partner strip’s Custom module, so that a screen reader announces it as a region. Select the child template, open Create Overrides, choose mod_custom in the Modules group, and edit the created file:

<?php

/**
 * @package     Joomla.Site
 * @subpackage  mod_custom
 *
 * Override in cassiopeia_tallpine/html/mod_custom/default.php
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Uri\Uri;

$modId = 'mod-custom' . $module->id;

if ($params->get('backgroundimage')) {
    /** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
    $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
    $wa->addInlineStyle('
#' . $modId . '{background-image: url("' . Uri::root(true) . '/' . HTMLHelper::_('cleanImageURL', $params->get('backgroundimage'))->url . '");}
', ['name' => $modId]);
}

$isStrip = $module->position === 'partner-strip';
?>

<div id="<?php echo $modId; ?>" class="mod-custom custom"
    <?php if ($isStrip) : ?>role="region" aria-label="<?php echo htmlspecialchars($module->title, ENT_QUOTES, 'UTF-8'); ?>"<?php endif; ?>>
    <?php echo $module->content; ?>
</div>

The second override changes how a custom field renders as a distance badge; it is shown in full, with the reasoning about field context, in Custom fields. It lives at html/layouts/com_fields/field/render.php of the child. The point for this page is the lookup: the layout system finds the child’s file first, and Cassiopeia, which has no such override, never takes part. Keep the club’s overrides in the child, never in Cassiopeia (updates replace it), and keep their number small.

Step 5: check the result

  1. In Content, Site Modules, create a Custom module titled "Trail conditions", position trail-conditions, assigned to all pages and with Access set to the Trail Warden level if only wardens should see it.

  2. Append ?tp=1 to the site’s address (with Preview Module Positions enabled in the Templates options) and check that both new positions show in the preview.

  3. Disable the Trail alert parameter in the style and confirm the notice disappears.

  4. Rename user.css temporarily: the page should lose the club’s colours but keep Cassiopeia’s own.

  5. Open the browser’s network panel and confirm that tallpine.css, user.css and tallpine.js come from media/templates/site/cassiopeia_tallpine/, and that Cassiopeia’s template.min.css still comes from media/templates/site/cassiopeia/.

  6. Check the page in German and in the dark colour mode; text and focus indicators must stay visible.

Keeping a child up to date

  • Cassiopeia changes. Parent files you did not override keep updating. Read the release notes of each minor release and compare your overrides and, if you included it, the markup fragments your index.php relies on. The upgrade pages for the major versions (see Upgrading Joomla 5 to 6) list template-relevant changes.

  • Automated core updates. A site that applies updates by itself replaces only the parent; the child’s folders are not touched. See Updating Joomla and Automated Core Updates.

  • Version your own template. Change <version> in the manifest and the version of your assets on each release, so that browsers fetch the new files.

  • Backups. Back up templates/cassiopeia_tallpine and media/templates/site/cassiopeia_tallpine together with the database, because the style parameters live in the database and the files on disk.

Security notes

  • A template is executable PHP. Anyone with core.edit on the Templates component can change index.php and overrides from the Administrator and therefore run code. Restrict that permission to Super Users or a small role and prefer version control and deployment over editing live files.

  • Escape every value you print: parameters with htmlspecialchars() (as above), and module content only as Joomla prepared it. Do not echo request input.

  • Start every PHP file with defined('_JEXEC') or die;, as the examples do.

  • Do not hot-link fonts, scripts or analytics from third-party hosts; serve them from the child’s media folder, which keeps the page within a strict Content Security Policy and avoids sending visitor data elsewhere.

  • Files in media/ are public. Keep keys, API tokens and credentials out of a template, in the child as in the parent.

Common errors and pitfalls

  • The child shows nothing different. The CSS file is in templates/cassiopeia_tallpine/css instead of the media folder, or was edited in the package’s media source but not copied to media/templates/site/cassiopeia_tallpine.

  • "Create Child Template" is missing. The selected template is not inheritable. Only templates with inheritable set to 1 can be parents.

  • Invalid name error. The name contains a hyphen, a space or an accented letter. Use letters, digits and underscores.

  • The module editor lacks the parent’s positions. A hand-written child omitted the parent’s <position> entries; the editor reads the child’s manifest only.

  • Style form has lost fields and the layout looks wrong. The child’s <config> omitted fields that the parent’s index.php reads. Keep them.

  • Parent label unchanged. A language key was spelled differently; copy it exactly from the parent’s .ini file.

  • A new position shows in the editor but not on the page. The child has no index.php that outputs it.

  • Two templates both inheritable. Setting inheritable to 1 in a child makes it appear as a parent candidate. Keep it 0.

  • Edits vanish after reinstalling the package. Live edits were made under media/templates/site/... and the package overwrote them; keep sources in version control.

  • Assets load from the wrong place. An asset uri starting with / or http is not resolved through the child-then-parent lookup; use relative names for template files.

Version notes

  • Joomla 4.1 introduced child templates and the Create Child Template button. Joomla 4.0 had only copies.

  • Joomla 5.0 removed the ES5 assets, so a child needs no nomodule scripts. The Templates manager works as above in 5.4.

  • Joomla 5.1 lets a template override the Menu Assignment tab of its style form from the <config> section (see the manifest page of the Manual); a child can use this too.

  • Joomla 6.0 ships cassiopeia_extended, a core child template. The legacy J* class aliases (JFactory, JText and so on) are off by default on new installs and available only through the Behaviour - Backward Compatibility 6 plugin, so a child template’s PHP should use the namespaced classes (Joomla\CMS\...) throughout.

  • Joomla 6.1 adds "version": "nocache" for assets.

  • Joomla 6.2 requires PHP 8.3 or newer; template code needs no change for that unless it relied on removed PHP features.

Corrections to older books and tutorials

  • Joomla! 4 Masterclass (2023) presents child templates as brand new and was written against Joomla 4, which has been end of life since October 2025. Treat child templates as the established way to customise Cassiopeia; the current documentation is the User Guide and the Developer Manual rather than docs.joomla.org pages for J4.x.

  • Tutorials that tell you to edit templates/cassiopeia/css/template.css or to put a user.css into the parent are describing changes that disappear at the next update. Put the file in the child’s media folder.

  • Tutorials that copy Cassiopeia under a new name describe a copied template; use a child unless you intend to maintain a separate template.

  • Developing Extensions for Joomla! 5 (2023) covers overrides and child templates for Joomla 5.0. Template code that still uses the J* aliases must be moved to namespaced classes for 6.x (see Upgrading Joomla 5 to 6).