Joomla Web Asset Manager

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.

Every Joomla page is made of HTML plus a set of stylesheets and scripts. Joomla 4 replaced the old habit of calling addStyleSheet() and addScript() from anywhere with the Web Asset Manager: a registry of named assets (a style, a script or a preset), each with declared dependencies, which extensions and templates use by name and which the document renders into the page head in dependency order. This page explains the whole mechanism: the joomla.asset.json file format, the five registry files and the order in which Joomla reads them, the PHP API (useStyle(), useScript(), usePreset() and friends), inline assets, versioning and cache busting, ES modules and import maps, web components, how to override or disable a core asset, the output attributes (defer, type="module"), the security implications and the errors you will meet. It was checked against the official Developer Manual and the 6.2 source tree on 2026-10-30. The behaviour described is that of Joomla 6.2 (current, PHP 8.3 or newer); where Joomla 5.4 (security fixes only) differs, the text says so. 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 child template cassiopeia_tallpine (see Child templates) needs its own stylesheet and script, a trail-map ES module, a fallback for old browsers, a front-end module mod_tallpine_nexthikes with its own CSS, and a way to switch off a core asset it does not use. The site is built step by step across the sub-section; this page is the part about assets. The accessibility consequences of scripts and styles (focus order, reduced motion, contrast) are covered in the general Accessibility page and, for Joomla templates, in Colour modes, RTL and accessibility.

What the Web Asset Manager is, and who uses it

The Web Asset Manager (WAM) is a PHP service, Joomla\CMS\WebAsset\WebAssetManager, attached to every HTML document. It sits on top of a WebAssetRegistry that knows every asset that can be loaded, and it keeps track of the assets that will be loaded on the current request. Four kinds of people touch it:

  • The themer writes a joomla.asset.json for the template and calls the manager from the template’s index.php.

  • The extension developer ships a joomla.asset.json in the extension’s media folder and calls the manager from a view, a layout, a module dispatcher or a plugin.

  • The site builder rarely edits assets directly but sees them in the page source, in the debug bar and when a template override breaks a page.

  • The administrator of a performance- or security-minded site decides which core assets to keep, how they are cached and how they are allowed under a Content Security Policy.

Older material predates it. Tutorials written for Joomla 3 call JHtml::_('jquery.framework'), JHtml::script() or $document->addScript(); the Joomla 4 Developer Manual calls the Document::addScript() and Document::addStyleSheet() family deprecated, to be removed in Joomla 6, and tells you to use the manager instead. The book Joomla! 4 Masterclass (2023) was written for Joomla 4.x and so predates import maps, the removal of the ES5 assets and the nocache version of Joomla 6.1; Developing Extensions for Joomla! 5 (2023) covers the manager itself well but was written for Joomla 5.0 and PHP 8.2, so its version statements need the corrections that follow in this page. What has not changed is the important part: Text::script() and Document::addScriptOptions() are not deprecated and remain the way to hand translated strings and variables to JavaScript.

The three stages: register, use, render

Everything the manager does happens in three stages.

flowchart LR R[Registry files<br/>joomla.asset.json] -->|1 register| REG[(WebAssetRegistry<br/>known assets)] CODE[Template, view, layout,<br/>module, plugin] -->|2 useStyle, useScript,<br/>usePreset| WAM[WebAssetManager<br/>active assets] REG --> WAM WAM -->|resolve dependencies,<br/>sort, version| REN[Renderers] REN -->|3 render| H[jdoc:include styles,<br/>jdoc:include scripts]
  • Register. While the application boots, Joomla reads a series of joomla.asset.json files into the registry. A registered asset is only a description (name, type, file, dependencies, attributes); nothing is output for it.

  • Use. Code asks the manager to activate assets by name, for example useScript('field.modal-fields'). The manager marks the asset active and, lazily, every asset it depends on as active as a dependency. Using a name that is not registered throws an UnknownAssetException.

  • Render. When the template’s head is built, the <jdoc:include type="styles" /> and <jdoc:include type="scripts" /> tags are replaced by the <link> and <script> elements of the active assets, sorted so that dependencies come first, with their attributes and a version query string.

The two renderers matter because they explain a frequent surprise: an asset activated after the head has been rendered is never output. In a template the <jdoc:include> tags are replaced after index.php ran, so calling useScript() anywhere in index.php, in a layout or in a module is early enough. A call made from a plugin that runs after onBeforeCompileHead is not.

The joomla.asset.json file

A registry file is plain JSON. The $schema property points at the schema Joomla publishes for editors, and the top-level object has a name, a version, a description, a license, an optional namespace (explained under custom asset classes) and the assets array.

Each entry of assets is an object. The keys you will use are:

Key Meaning

name

The registry key. Required. A style and a script may share a name, because the registry is keyed by type and name.

type

Required: style, script or preset.

uri

The file, relative to the extension’s media folder and without the js/ or css/ sub-folder, which Joomla inserts from the type. com_tallpine/trail-map.css means media/com_tallpine/css/trail-map.css. A full https:// URL or a protocol-relative // URL is used unchanged. An empty string registers an asset with no file, used for presets and for dummy assets that only carry dependencies.

version

auto (the default), nocache, or any fixed string. See the versioning section.

dependencies

An array of asset names to use first. In a preset the entries are suffixed #style or #script.

attributes

Attributes copied onto the generated element: defer, async, type, nomodule, integrity, crossorigin, data-* and so on.

weight

A number used to order assets that have no dependency relation; the core template uses 500 for the user’s own CSS so that it comes last.

deprecated, deprecatedMsg

Marks the asset deprecated. In debug mode (JDEBUG) Joomla raises an E_USER_DEPRECATED notice with the message when the asset is rendered.

importmap, importmapName, importmapScope

Add a script to the page’s import map (see the ES modules section).

namespace, class

Choose a custom WebAssetItem class (see the custom classes section).

Keys that the manager does not interpret are kept as options of the asset and passed along, which is how importmap, weight, deprecated and version work. Unrecognised keys therefore do no harm, but they do nothing either. A missing name or type makes the whole file fail with a RuntimeException, and so does invalid JSON: a stray comma in the template’s joomla.asset.json takes the front end down on every page, not just on the page that uses the asset.

A first file for the child template

The cassiopeia_tallpine child template ships a stylesheet that sets the club’s colours and a script that adds a "back to the trailhead" link behaviour. It keeps its assets in its own media folder, media/templates/site/cassiopeia_tallpine, which the template manifest installs from its media folder.

{
  "$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 colours, typography and trail badges.",
      "type": "style",
      "uri": "tallpine.css",
      "dependencies": [
        "template.cassiopeia.ltr#style"
      ]
    },
    {
      "name": "template.tallpine",
      "description": "Small enhancements: back to trailhead, collapsible weather strip.",
      "type": "script",
      "uri": "tallpine.js",
      "attributes": {
        "type": "module"
      },
      "dependencies": [
        "core"
      ]
    },
    {
      "name": "template.tallpine",
      "type": "preset",
      "dependencies": [
        "template.tallpine#style",
        "template.tallpine#script"
      ]
    }
  ]
}

The file lives in the template root (templates/cassiopeia_tallpine/joomla.asset.json) and must be listed in the <files> block of templateDetails.xml like any other template file; the CSS and JavaScript live under media/templates/site/cassiopeia_tallpine/css/ and .../js/. The three entries share one name, template.tallpine. That is legal because the registry is keyed by type: one name is a style, a script and a preset at the same time, and usePreset('template.tallpine') activates the style and the script together.

The core template follows the same pattern. Cassiopeia’s own file registers template.cassiopeia.ltr and template.cassiopeia.rtl styles, a template.cassiopeia module script that depends on core, two dummy template.active assets that extensions can depend on to load "after the active template", a template.user style and script for the site owner’s user.css and user.js, and presets that pair the right-to-left or left-to-right style with the script. Reading that file is the best documentation of the idea.

Where assets are found: the registry lookup order

Joomla does not scan the file system for asset files. It reads a fixed list of registry files, in a fixed order, and later definitions replace earlier ones with the same type and name. The order is:

  1. media/vendor/joomla.asset.json: third-party libraries bundled with Joomla (loaded the first time the registry is used).

  2. media/system/joomla.asset.json: Joomla’s own core assets (core, form validation, the dialog, the editors and so on).

  3. media/legacy/joomla.asset.json: assets kept for backwards compatibility.

  4. media/<component>/joomla.asset.json: the active component’s file, for example media/com_tallpine/joomla.asset.json, added when the component is dispatched.

  5. templates/<template>/joomla.asset.json: the active template’s file. For a child template Joomla adds the parent’s file first and then the child’s, so the child can override the parent.

flowchart TB V["1 media/vendor"] --> S["2 media/system"] S --> L["3 media/legacy"] L --> C["4 media/com_tallpine<br/>active component"] C --> P["5a templates/cassiopeia<br/>parent template"] P --> T["5b templates/cassiopeia_tallpine<br/>child template"] T --> W["Registry: last definition of a name wins"]

Two consequences follow. First, a template can override any asset a component or the core registered, simply by declaring one with the same name, which is what the overriding section below uses. Second, anything that is not on the list is not loaded automatically. Modules, plugins and layouts that ship a joomla.asset.json must add it themselves with addRegistryFile(), because the manager only reads the files of the active component and template. A module author who forgets this sees an UnknownAssetException as soon as useStyle('mod_tallpine_nexthikes') runs.

The paths given to addRegistryFile() are relative to the site root, for example media/mod_tallpine_nexthikes/joomla.asset.json. A site that uses the public folder (see Installation and the public folder) still writes the same relative path.

Where the asset files themselves come from

The uri of an asset is resolved when the page is rendered. For a relative uri, Joomla looks at the template’s media folder first (the child’s, then the parent’s), then at the extension’s media folder. In particular:

  • tallpine.css registered by the template resolves to media/templates/site/cassiopeia_tallpine/css/tallpine.css, and a file with the same name in the parent’s media folder is used if the child has none.

  • com_tallpine/trail-map.css resolves to media/com_tallpine/css/trail-map.css.

  • system/joomla-fontawesome.min.css resolves to media/system/css/joomla-fontawesome.min.css.

  • A uri whose file exists under the site root (it contains a / and the file is found there) is treated as an absolute path from the site root; anything starting with http://, https:// or // is external and left alone.

There are two debug-related behaviours. When debug is off, a request for template.css loads template.min.css if it exists; when JDEBUG is on, the unminified file is loaded instead, which is why the core ships both. And because a template’s media folder is searched before the extension’s, a template can replace a core file by placing a file at the same relative path under its own media folder (for example css/system/searchtools/searchtools.min.css) without touching the registry at all. That is a file override, whereas redefining the asset is a registry override; the overriding section compares them.

Using assets from PHP

The manager is obtained from the document. In a template, the document is $this; elsewhere you ask the application.

<?php
use Joomla\CMS\Factory;

$wa = Factory::getApplication()->getDocument()->getWebAssetManager();

Inside a class that already has the application (a dispatcher, a plugin with getApplication(), a view with $this->getDocument()), prefer the injected application or document to the static Factory; the call chain is the same.

The calls are generated from three verbs and three types. use, disable, register and registerAndUse combine with Style, Script and Preset, and all return the manager so they can be chained.

Call Effect

useStyle($name), useScript($name), usePreset($name)

Activates a registered asset. Its dependencies are activated as dependencies.

disableStyle($name), disableScript($name), disablePreset($name)

Deactivates an asset that was activated directly. A dependency of another active asset is brought back.

registerStyle($name, $uri, $options, $attributes, $dependencies), registerScript(...), registerPreset(...)

Adds or replaces a registry entry at run time.

registerAndUseStyle(...), registerAndUseScript(...), registerAndUsePreset(...)

Registers and activates in one call.

addInlineStyle($content, $options, $attributes, $dependencies), addInlineScript(...)

Adds an inline <style> or <script> element.

assetExists($type, $name), isAssetActive($type, $name), getAssetState($type, $name), getAsset($type, $name)

Introspection. getAssetState() returns active (enabled by a call) or dependency (enabled because something else needs it).

The state of an asset is therefore one of inactive, active or dependency. Dependencies are resolved lazily, the first time the manager is asked for its assets or for a state, and again whenever the set of active assets changes. Resolution is recursive; an asset that lists itself is ignored; a missing dependency throws UnsatisfiedDependencyException.

In the child template’s index.php

The child template overrides index.php of Cassiopeia (see Template overrides and alternative layouts) and adds its own assets to the ones Cassiopeia activates.

<?php
// templates/cassiopeia_tallpine/index.php (excerpt)

\defined('_JEXEC') or die;

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

/** @var Joomla\CMS\Document\HtmlDocument $this */
$wa = $this->getWebAssetManager();

// Parent template assets (the preset pairs the stylesheet with template.js)
$wa->usePreset('template.cassiopeia.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr'))
    ->useStyle('template.active.language')
    ->useStyle('template.user')
    ->useScript('template.user');

// Child template assets, declared in templates/cassiopeia_tallpine/joomla.asset.json
$wa->usePreset('template.tallpine');

// Colour scheme chosen in the template style: register and use a file of the child's media folder
$scheme = $this->params->get('colourScheme', 'forest');
$wa->registerAndUseStyle(
    'theme.tallpine.' . $scheme,
    'global/' . $scheme . '.css',
    ['version' => 'auto']
);

Two details come from how Cassiopeia itself is written. It registers the colour theme with registerAndUseStyle() using a file name taken from a template parameter, and it re-registers the dummy template.active style so that its dependency is the left-to-right or right-to-left preset; every extension that depends on template.active therefore loads after the right stylesheet. A child that replaces the stylesheet should keep that contract.

In a component view or layout

A component registers its assets in the component’s own joomla.asset.json (media/com_tallpine/joomla.asset.json, installed from the extension’s media folder) and activates them from the view. The file is added automatically because com_tallpine is the active component.

{
  "$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json",
  "name": "com_tallpine",
  "version": "1.0.0",
  "description": "Assets of the Tallpine trails component.",
  "license": "GPL-2.0-or-later",
  "assets": [
    {
      "name": "com_tallpine.trail-list",
      "type": "style",
      "uri": "com_tallpine/trail-list.css"
    },
    {
      "name": "com_tallpine.trail-map",
      "type": "script",
      "uri": "com_tallpine/trail-map.js",
      "attributes": {
        "type": "module"
      },
      "dependencies": [
        "core",
        "com_tallpine.map-style"
      ]
    },
    {
      "name": "com_tallpine.map-style",
      "type": "style",
      "uri": "com_tallpine/trail-map.css"
    }
  ]
}
<?php
// components/com_tallpine/tmpl/trails/default.php (excerpt)

\defined('_JEXEC') or die;

use Joomla\CMS\Language\Text;

/** @var Tallpine\Component\Tallpine\Site\View\Trails\HtmlView $this */
$wa = $this->getDocument()->getWebAssetManager();
$wa->useStyle('com_tallpine.trail-list')
    ->useScript('com_tallpine.trail-map');

// Translated strings and data for the module script, never concatenated into inline JavaScript
Text::script('COM_TALLPINE_MAP_LOADING');
Text::script('COM_TALLPINE_MAP_NO_TRAILS');
$this->getDocument()->addScriptOptions('com_tallpine.map', [
    'center' => [46.52, 8.01],
    'zoom'   => 11,
    'api'    => 'index.php?option=com_tallpine&view=trails&format=json',
]);

Naming assets with the extension prefix (com_tallpine.trail-map) avoids clashes in the shared registry: the last registration of a name wins, so a generic name such as map can silently replace someone else’s asset.

In a module

A module is not scanned automatically, so its dispatcher registers its file before using it.

<?php
// modules/mod_tallpine_nexthikes/src/Dispatcher/Dispatcher.php (excerpt)

namespace Tallpine\Module\NextHikes\Site\Dispatcher;

\defined('_JEXEC') or die;

use Joomla\CMS\Dispatcher\AbstractModuleDispatcher;

class Dispatcher extends AbstractModuleDispatcher
{
    protected function getLayoutData(): array
    {
        $data = parent::getLayoutData();

        $wa = $this->getApplication()->getDocument()->getWebAssetManager();
        $wa->getRegistry()->addRegistryFile('media/mod_tallpine_nexthikes/joomla.asset.json');
        $wa->useStyle('mod_tallpine_nexthikes');

        return $data;
    }
}

The module’s own file has a single style entry named mod_tallpine_nexthikes. Writing modules is covered in Module development and layout-side details in Layouts and module chromes.

Presets

A preset is an asset of type preset whose job is to group others. Its dependencies list names of other assets, each followed by the type to use (name#style or name#script); a preset has no file, so its uri is empty. Activating the preset activates every listed item. The manager methods are usePreset(), disablePreset(), registerPreset() and registerAndUsePreset().

Presets answer a real maintenance problem: Cassiopeia needs a style and a script that always go together, and the left to right and right to left variants differ only in the style. Two presets keep that decision in one place. For the club’s template, template.tallpine does the same for its style and script. If an entry of a preset cannot be found, the manager throws UnsatisfiedDependencyException when the preset is used, not when it is registered. Presets are not sorted: their items are activated in the order listed, and the ordinary dependency sort then arranges the resulting assets.

Dependencies and ordering

The order of elements in the page is computed, not written. For each type, the manager takes the active assets plus their dependencies and sorts them with a topological sort: a dependency always comes before the asset that needs it, and assets with no relation to each other come out in the order they were activated (first in, first out), nudged by the weight option when one is present. Cassiopeia gives the owner’s user.css a weight of 500 so that it is last. This has practical rules:

  • Declare what you need in dependencies, not by activation order. A script that uses Joomla.Text depends on core; a script that uses a form-validation helper depends on form.validate; a script that needs jQuery depends on jquery. jQuery is not loaded by default in Joomla 4 and later, which differs from the Joomla 3 behaviour that tutorials assume.

  • A dependency cycle cannot be sorted. Keep the graph a tree.

  • The template.active dummy asset is the supported way to say "load after the template’s stylesheet".

Versions and cache busting

Every asset has a version that controls the query string appended to its URL, so that browsers and proxies fetch a new file after an update.

version Output

auto (default)

The document’s media version is appended (? followed by a hash that Joomla derives for the site). It changes when Joomla is updated or the media version is refreshed, which keeps caches valid in between.

nocache (Joomla 6.1)

A fresh value of the form nocache- plus a random number is appended on every request, so the file is never served from a browser or proxy cache. It exists for development, for assets that change per request, and for files you are editing on a running site.

any other string

Appended as is, for example 1.4.2, which is the right choice for a library with its own release number.

The query string is added only if the URL does not already contain a ?. The same rule applies to the src of the import map. For nocache the manifest is simply "version": "nocache":

{
  "name": "template.tallpine.dev",
  "type": "style",
  "uri": "tallpine-dev.css",
  "version": "nocache"
}

The nocache value is a development tool. In production it defeats browser caching for that asset and, because the URL changes on every request, a reverse proxy or CDN cannot cache the page’s references to it consistently either; remove it before going live (see Caching and performance). A template that must also run on Joomla 5.4 should not rely on the value there, because the 6.1 release notes and Manual place it in the 6.x line; set a fixed version through registerAndUseStyle(..., ['version' => ...]) from PHP for that case.

Attributes: defer, async, module and nomodule

Everything in attributes is copied to the element. The commonly useful ones are:

  • "defer": true or "async": true on a script. Joomla’s own scripts are mostly deferred or modules; a script with type: module is deferred by the browser anyway.

  • "type": "module" marks an ES module. Cassiopeia’s template.cassiopeia script uses it.

  • "nomodule": true marks a script that only browsers without module support execute.

  • "integrity" and "crossorigin" for subresource integrity on a file from another host.

  • "rel": "lazy-stylesheet" on a style, which Cassiopeia uses for Font Awesome. Joomla’s own script turns it into a normal stylesheet once the page has loaded, so the icons do not block rendering. A site that does not load that script must not use the value.

  • data-* attributes read by the script.

{
  "name": "tallpine.trail-widget.legacy",
  "type": "script",
  "uri": "com_tallpine/trail-widget-legacy.js",
  "attributes": {
    "nomodule": true,
    "defer": true
  }
},
{
  "name": "tallpine.trail-widget",
  "type": "script",
  "uri": "com_tallpine/trail-widget.js",
  "attributes": {
    "type": "module"
  },
  "dependencies": [
    "tallpine.trail-widget.legacy"
  ]
}

This module-plus-legacy-fallback pattern is how Joomla itself shipped assets in Joomla 4. In Joomla 5.0 the project dropped the ES5 builds of its own assets: modern browsers all understand modules, so the nomodule variants of core assets were removed. The consequences are practical. Third-party extensions and old templates that depended on a core ES5 asset by name must stop; new assets should be written as modules and need no fallback unless the club genuinely supports a browser without module support, which is not the case for any browser Joomla 6 itself supports. Where you still use a nomodule script, keep it a leaf dependency of the module, as above. In a debug session core assets carry data-asset-name and data-asset-dependencies attributes so you can see which asset produced which element.

For script assets, a boolean attribute written as true is output as a bare attribute. If a template needs a non-boolean attribute at run time, $wa->getAsset('style', 'fontawesome')->setAttribute('rel', 'lazy-stylesheet') sets it on the registry item, as Cassiopeia’s own index.php does.

Inline assets

Sometimes the content is not a file: a few CSS custom properties computed from template parameters, or a three-line initialisation script. addInlineStyle() and addInlineScript() register an inline asset:

<?php
// templates/cassiopeia_tallpine/index.php (excerpt)

$accent = $this->params->get('accentColour', '#2d6a4f');

// Validate before output: only a six digit hexadecimal colour may reach the page
if (!preg_match('/^#[0-9a-fA-F]{6}$/', $accent)) {
    $accent = '#2d6a4f';
}

$wa->addInlineStyle(
    ':root { --tallpine-accent: ' . $accent . '; }',
    ['name' => 'tallpine.accent', 'position' => 'before'],
    [],
    ['template.tallpine#style'] // placed relative to the last dependency
);

$wa->addInlineScript(
    'document.documentElement.classList.add("js-ready");',
    ['name' => 'tallpine.js-ready'],
    ['type' => 'module'],
    ['template.tallpine']
);

The rules, all from the Manual and the renderer:

  • position is before or after (the default). It places the inline content before or after the file asset named as the last dependency; with no dependency, after all other assets of that type.

  • name is optional. Without a name Joomla uses a hash of the content, which makes identical inline assets collapse into one; give a name when a later call should be able to refer to or override the asset.

  • Inline assets can be dependencies of other inline assets. Using one as a dependency of a file asset works but its behaviour is documented as subject to change, so state a position instead.

  • A dependency that is not registered throws UnsatisfiedDependencyException.

  • attributes apply too, so ['type' => 'module'] makes an inline module.

  • Inline assets in a joomla.asset.json are possible but discouraged; use files.

Inline code is a security surface, so treat it as one. If the content includes any value that came from a request, a database row or a parameter, it must be validated or encoded for its context, as in the colour check above. Where the data is more than a fixed constant, do not build JavaScript with string concatenation: put the data in addScriptOptions() and read it with Joomla.getOptions('com_tallpine.map'), and put translatable text in Text::script(). When a Content Security Policy is active (see Security hardening, MFA and passkeys), inline assets are the first thing it blocks; Joomla adds the document’s CSP nonce to every script and style element it renders when one is set, which is another reason to use the manager rather than printing <script> tags in a layout.

ES modules and import maps

A script asset declared with "type": "module" is a native ES module. Modules can import each other by bare names (import {x} from 'tallpine-map'), and browsers resolve those names with an import map, a JSON block in the page head. Joomla builds the import map for you from the registry.

Add "importmap": true to a script asset and Joomla includes it in the map. Two optional options refine it: importmapName sets the name other modules import (the default is the asset name) and importmapScope limits the entry to modules loaded from a path. This arrived with Joomla 5.0, together with the polyfill that makes import maps work in older browsers; core itself uses it for the editor API (editor-decorator, editor-api) and the dialog (joomla.dialog).

{
  "name": "tallpine.map-core",
  "type": "script",
  "uri": "com_tallpine/map-core.js",
  "attributes": {
    "type": "module"
  },
  "importmap": true,
  "importmapName": "@tallpine/map-core"
},
{
  "name": "com_tallpine.trail-map",
  "type": "script",
  "uri": "com_tallpine/trail-map.js",
  "attributes": {
    "type": "module"
  },
  "dependencies": [
    "core",
    "tallpine.map-core"
  ]
}
// media/com_tallpine/js/trail-map.js
import { createMap } from '@tallpine/map-core';

const options = Joomla.getOptions('com_tallpine.map');
const container = document.getElementById('tallpine-map');

if (container && options) {
  createMap(container, options);
}

The renderer emits the import map first, before any <script> element, as <script type="importmap"> with a JSON body mapping each name to the asset’s URL (with its version query string), plus a scopes object for scoped entries. The map must come before the first module that uses it, which is why Joomla renders it before the regular scripts. The same version rules apply to its URLs. Because the browser only reads the first import map in a document, do not add your own <script type="importmap"> to a layout: register an asset instead. An entry in the map does not load the file, it only names it; the file loads when a module imports it, or when the asset is also used so that a <script> element is emitted.

Web components

Joomla ships a loader for custom elements (joomla-alert, joomla-tab, joomla-field-media and so on). By convention the asset is called webcomponent.<name>, is a module script plus a style, and is loaded asynchronously by the Web Component loader instead of as an ordinary script. A fallback for browsers without module support lists a nomodule script that depends on the wcpolyfill asset; Joomla 6.0 removed the polyfill asset, and the Behaviour - Backward Compatibility 6 plugin keeps an empty placeholder with that name (and for the dropped chosen assets) so that an old useScript('wcpolyfill') call does not throw. In PHP the pair is registered as a style plus a module script and then used together.

<?php
// Registering a small custom element of the Tallpine template at run time
$wa->registerStyle('webcomponent.tallpine-weather', 'tallpine-weather.css')
    ->registerScript(
        'webcomponent.tallpine-weather',
        'tallpine-weather.js',
        [],
        ['type' => 'module']
    );

$wa->useStyle('webcomponent.tallpine-weather')
    ->useScript('webcomponent.tallpine-weather');

Overriding and disabling core assets

The cleanest way to change what Joomla loads is the registry: define an asset with the same type and name in a file that is read later. Because the child template’s file is read last, that is the natural place.

Replace a core asset

The club restyles the search tools of the administrator-independent front end and wants its own stylesheet used wherever the core one is requested:

{
  "name": "searchtools",
  "type": "style",
  "uri": "tallpine-searchtools.css"
}

Placing this entry in templates/cassiopeia_tallpine/joomla.asset.json makes every extension that uses searchtools load the template’s file (media/templates/site/cassiopeia_tallpine/css/tallpine-searchtools.css) instead of the core one, because the template registry is loaded after media/system. The same can be done in code with $wa->registerStyle('searchtools', 'tallpine-searchtools.css') before the asset is used. Redefining an asset keeps the dependents working because they refer to it by name. It does not keep the old dependencies: the new entry replaces the whole definition, so repeat any dependencies the original had.

Disable a core asset

disableStyle('name') only removes an asset that was activated directly. If something else depends on it, the manager puts it back as a dependency. Cassiopeia’s stylesheet depends on fontawesome, so disableStyle('fontawesome') in a child template does nothing visible. There are three honest ways out:

  1. Redefine it as empty. Declare fontawesome in the child’s file with an empty uri (""): the asset still exists, so dependents are satisfied, but it has no file to output. Joomla uses this technique for its own dummy assets, and the Backward Compatibility 6 plugin ships such placeholders.

  2. Stop depending on it. Register a replacement for the parent’s style that omits the dependency, for example redefine template.cassiopeia.ltr in the child with its own dependencies list.

  3. Defer it. Set the rel="lazy-stylesheet" attribute, as Cassiopeia does, when you only want it out of the critical path.

{
  "name": "fontawesome",
  "description": "Not used by the Tallpine template: icons come from the club's own SVG sprite.",
  "type": "style",
  "uri": ""
}

Before removing a core asset, check the front end and the administrator interface of every extension the site uses: modules and component layouts that print Font Awesome classes show empty squares without it, and removing core breaks the scripts that call Joomla.Text, Joomla.getOptions and the other core functions.

File-level and registry-level overrides compared

Approach What it changes Use it when

Redefine the asset in the template’s joomla.asset.json

The asset’s URL, dependencies, attributes and version, for every user of the name

You change how an asset loads or want your file used instead of the core one

Place a file in the template’s media folder at the core file’s relative path

The content of the file served, with the registry untouched

You patch a single file (a core stylesheet) and want everything else, including dependencies, to stay as is

Call disable* or register* in PHP

One request, from the code that runs

The decision depends on the page or the template parameters

A third-party extension’s asset can be overridden the same way as a core one, which is why extension authors should use prefixed names. An override survives core updates but not changes in the asset’s name: after a major update, check that the names you override still exist (see Upgrading from Joomla 5 to 6).

Legacy methods and the move away from them

The Developer Manual lists Document::addScript(), Document::addStyleSheet() and the related addScriptDeclaration() and addStyleDeclaration() calls as deprecated, with removal scheduled for Joomla 6, and the manager as the replacement. In practice, treat every remaining call as technical debt: they bypass dependency resolution, have no version control and are output in a different part of the renderer. The mapping is mechanical:

Old code Replace with

$doc->addStyleSheet('...')

Register the file in joomla.asset.json or with registerAndUseStyle()

$doc->addScript('...', [], ['defer' => true])

registerAndUseScript() with ['defer' => true] as the attributes

$doc->addScriptDeclaration('...') / addStyleDeclaration('...')

addInlineScript() / addInlineStyle()

HTMLHelper::_('jquery.framework')

useScript('jquery')

Text::script(), addScriptOptions()

Keep: they are not deprecated

The compatibility rules for the move to Joomla 6, including how the Backward Compatibility 6 plugin helps while you migrate, are in Upgrading from Joomla 5 to 6 and Deprecations and preparing for Joomla 7.

Custom asset classes

The default item class is Joomla\CMS\WebAsset\WebAssetItem. An asset can name another class with class (and optionally namespace), which must implement WebAssetItemInterface or extend WebAssetItem. The namespace set at the top of the file applies to every item, a namespace on an item overrides it, and with none given the default namespace is Joomla\CMS\WebAsset; an explicitly empty namespace means the class name is used as it is. Joomla uses this for core (CoreAssetItem), the keepalive script and Cassiopeia’s language-specific stylesheet (LangActiveAssetItem). Two reasons to write one:

  • Choose a different file at run time. Override getUri(), for example to return tallpine-de.css when the active language is German. Call resolvePath() from it when the path should be resolved the normal way.

  • Run code when the asset is attached. Implement WebAssetAttachBehaviorInterface; its onAttachCallback(Document $doc) is called when the asset is attached to the document and can add script options. The asset must have been used before the onBeforeCompileHead event, otherwise the callback is ignored.

Debugging and testing

With Docker Compose running (see Local development with Docker), two checks cover most problems. Switch on System > Global Configuration > Debug System, then look at the generated head: assets carry data-asset-name and data-asset-dependencies.

# List every stylesheet and script the home page requests, with versions
curl -s http://localhost:8080/ | grep -oE '<(link|script)[^>]+(href|src)="[^"]+"'

# The import map, if any asset opted in
curl -s http://localhost:8080/ | grep -A12 'type="importmap"'

The Chrome or Firefox network panel then shows whether the child template’s files returned 200 or 404; a 404 for media/templates/site/cassiopeia_tallpine/css/tallpine.css means the file is missing from the template’s media folder or the <media> element of templateDetails.xml does not list its css folder. See Debugging for the debug bar and the log.

Common errors and pitfalls

  • UnknownAssetException ("Unknown asset"). The name was never registered, the registry file is not on the lookup list (a module that forgot addRegistryFile()), or the type is wrong (a style called with useScript). Check assetExists().

  • UnsatisfiedDependencyException. An asset or preset lists a dependency that is not registered, or an inline asset named a missing one.

  • RuntimeException on every page. A joomla.asset.json contains invalid JSON, or an entry has no name or no type.

  • The asset is registered but not output. It was never used, was used after the head was rendered, or was disabled while being a direct activation. Dependencies are enabled automatically; the asset itself is not.

  • The override has no effect. The child template is not the active template for that menu item (check the template style assignment), the name differs by a character or by type, or an extension registers it again later at run time.

  • disableScript() does not remove the script. Something depends on it. Use a registry override with an empty uri.

  • Everything is cached after an update. The auto version only changes with the media version; for your own files use a fixed version string and bump it with each release.

  • The import map is ignored. A layout prints its own <script type="importmap">, or the module that needs the name is loaded before the map. Let the renderer produce it.

  • A script runs before the DOM exists. It is a classic script without defer. Use type: module, or defer.

  • The file does not load in debug mode only. Debug mode loads the unminified name; ship both the .js and the .min.js file.

  • Joomla 3 habits. jQuery is not loaded for free. Declare jquery as a dependency or, better, stop using it.

Security considerations

  • Content Security Policy. The renderer adds the document’s nonce to script and style elements it generates. Inline assets work under a nonce-based policy; scripts printed by hand in a layout do not.

  • Subresource integrity. For a file from another host, set integrity and crossorigin in attributes and pin a version in the URL. Prefer hosting libraries locally, which also avoids a third party seeing your visitors' requests; this is a privacy point for sites subject to data-protection law (see Privacy and action logs).

  • Inline assets and user data. Anything that reaches addInlineScript() or addInlineStyle() is output without escaping. Validate or encode it for its context, and prefer addScriptOptions(), which encodes JSON safely.

  • Registry files are code. A joomla.asset.json can name any class through class/namespace, and onAttachCallback runs PHP on every request where the asset is used. Only install assets from sources you trust, and review extensions that override core assets (see Security in extensions).

  • Cache keys. A stable version string lets a cache serve the same file to everybody; nocache and random query strings are a development aid and should never reach production.

Version notes

Version Change

4.0

The Web Asset Manager, registry files and joomla.asset.json are introduced; the legacy Document methods remain as a compatibility layer.

4.1

Child templates: the parent template’s registry file is read before the child’s.

5.0

ES5 assets and their fallbacks are removed from core; ES module import maps (importmap, importmapName, importmapScope) are available.

5.4 (security fixes only)

Legacy Document methods still present but deprecated; no nocache documented.

6.0

Document::addScript() and related methods slated for removal; the Backward Compatibility 6 plugin carries empty placeholder assets (wcpolyfill, chosen) for removed ones; cassiopeia_extended is a core child of Cassiopeia.

6.1

"version": "nocache" in an asset definition.

6.2 (current)

No change to the asset format.