You need one extra stylesheet, a small script for a slider, or a Google Font on a Joomla site, and the quickest fix you find online is to paste a <link> or <script> tag into the template’s index.php. It works until the next template update wipes it out, or until the file loads twice, in the wrong order, before the CSS it was meant to override.
Since Joomla 4, and still in Joomla 5 and Joomla 6, there is a proper tool for this job: the Web Asset Manager. It decides which CSS and JavaScript files load on a page, in what order, with which dependencies, and it makes sure each one loads only once. This guide shows how it works, how to use it without touching core files, and how to fix the most common mistakes.
What the Web Asset Manager actually does

Every CSS or JavaScript file in Joomla can be registered as a named asset. An asset has a name (for example template.user), a type (style, script or preset), a file path, and an optional list of other assets it depends on. When a page renders, Joomla looks at which assets were switched on, works out their dependencies, sorts them and prints the right <link> and <script> tags in the document head.
That gives you three practical benefits over hard-coded tags:
- Correct order. If your script depends on
core, Joomla printscorefirst, every time. - No duplicates. If a module and your template both ask for the same asset, it is printed once.
- Clean overrides. Any extension can switch an asset off or replace it by name, without editing the file that registered it.
Assets are defined in JSON files called joomla.asset.json. Joomla reads them automatically from a fixed list of places, including media/vendor/, media/system/, each component’s media folder and, most important for site owners, the active template’s folder: templates/{your_template}/joomla.asset.json.
Look at how Cassiopeia does it
The fastest way to understand the system is to open the default template’s own asset file. In Joomla 5 or 6, go to System → Site Templates → Cassiopeia Details and Files and open joomla.asset.json. You will find entries like this one:
{
"name": "template.user",
"description": "A file where a user can add their own css.",
"type": "style",
"uri": "user.css",
"weight": 500,
"dependencies": [
"template.active",
"template.active.language"
]
}
Notice that uri is just user.css, not a full path. For a template, Joomla resolves the file inside the template’s media folder and adds the css or js sub-folder for you, so this entry points to media/templates/site/cassiopeia/css/user.css. The dependencies make sure it is printed after the main template stylesheet, which is exactly what you want for overrides.
Then open index.php in the same template. Near the top you will see the manager being fetched and the assets being switched on:
$wa = $this->getWebAssetManager();
$wa->usePreset('template.cassiopeia.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr'))
->useStyle('template.active.language')
->useStyle('template.user')
->useScript('template.user');
Defining an asset in JSON only registers it. Nothing is printed until something calls useStyle(), useScript() or usePreset() on it.
Method 1: add your own CSS and JS with user.css and user.js

Because Cassiopeia already registers and switches on template.user for both CSS and JavaScript, you do not need to write any PHP to add your own code. You only need to create the files. Joomla skips an asset whose file does not exist, so the template works fine until you add them.
Step 1: create user.css
- Go to System → Site Templates and open Cassiopeia Details and Files (or your child template, see below).
- In the file tree, open the media section and select the
cssfolder. - Click New File, enter
useras the file name, choose css as the type and create it. - Add your rules and click Save.
/* media/templates/site/cassiopeia/css/user.css */
.header {
background: #0f172a;
}
.container-header .navbar-brand {
font-size: 1.75rem;
}
Reload the front end and view the page source. You should see user.css printed after template.min.css, with a version query string added for cache busting.
Step 2: create user.js (optional)
Repeat the same steps in the js folder and name the file user.js. It loads after the template’s own script because template.user depends on template.active.
// media/templates/site/cassiopeia/js/user.js
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('a[href^="http"]').forEach((link) => {
if (!link.href.startsWith(window.location.origin)) {
link.setAttribute('rel', 'noopener');
}
});
});
Use a child template so updates do not overwrite your files
Joomla updates can replace Cassiopeia’s files. To keep your work safe, create a child template first: open Cassiopeia Details and Files and click Create Child Template, give it a name, then assign the new template style to your site. Put user.css and user.js in the child template’s media folder. Our older guide on creating Joomla child templates walks through the full process, and the same steps apply in Joomla 5 and 6.
Method 2: register your own assets in joomla.asset.json
The user.css trick covers most small changes. When you are building or extending a template and need several named files, conditional loading or dependencies on Joomla’s own scripts, register them properly.
Step 1: put the files in the media folder
Upload your files to the template’s media folder, using the css and js sub-folders:
media/templates/site/your_template/css/slider.css
media/templates/site/your_template/js/slider.js
Step 2: describe them in joomla.asset.json

Open templates/your_template/joomla.asset.json and add entries to the assets array. Keep the existing ones as they are.
{
"name": "template.slider",
"type": "style",
"uri": "slider.css",
"dependencies": ["template.active"]
},
{
"name": "template.slider",
"type": "script",
"uri": "slider.js",
"attributes": {
"defer": true
},
"dependencies": ["core"]
},
{
"name": "template.slider",
"type": "preset",
"uri": "",
"dependencies": ["template.slider#style", "template.slider#script"]
}
A few details matter here:
- A style and a script may share the same name. The
#styleand#scriptsuffixes in a preset tell Joomla which one you mean. attributesare printed on the tag."defer": truekeeps the script from blocking rendering.coreis Joomla’s base script. Depend on it only if your code usesJoomla.*helpers; otherwise leave it out.- The file must remain valid JSON. A single trailing comma breaks every asset in the file, so validate it before saving (the
$schemaline at the top lets most code editors check it for you).
Step 3: switch the asset on
Registering is not enough; something has to use the asset. In the template’s index.php, after $wa is defined:
$wa->usePreset('template.slider');
Or load it only where it is needed, for example in a layout override for the home page module, so other pages stay lighter:
use Joomla\CMS\Factory;
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->usePreset('template.slider');
If you are new to layout overrides, see how to create template overrides in Joomla.
Method 3: register and use a file in one line of PHP
For a one-off file you do not want in the JSON, registerAndUseStyle() and registerAndUseScript() do both jobs at once. The arguments are the asset name, the file, options, HTML attributes and dependencies:
$wa->registerAndUseStyle(
'template.fonts',
'fonts.css',
['version' => 'auto'],
[],
['template.active']
);
$wa->registerAndUseScript(
'template.cookie-notice',
'cookie-notice.js',
[],
['defer' => true],
['core']
);
The 'version' => 'auto' option adds Joomla’s media version to the URL so browsers fetch the new file after you change it. Cassiopeia uses the same option for its font scheme stylesheet.
Inline CSS and JavaScript
Small snippets that depend on a template parameter, such as a brand colour chosen in the template style, can be added inline:
$brand = $this->params->get('brandColor', '#0f172a');
$wa->addInlineStyle(':root { --brand-color: ' . htmlspecialchars($brand) . '; }');
$wa->addInlineScript(
'window.siteConfig = { animate: true };',
['position' => 'before'],
[],
['template.slider']
);
With 'position' => 'before' and a dependency on template.slider, the inline script is printed just before the slider script, so the config exists when the slider starts. Keep inline code small. Anything longer than a few lines belongs in a file the browser can cache.
Removing or replacing assets you do not need
The Web Asset Manager also lets you switch things off by name. Two common cases:
- A template or extension loads a library you already include. Call
$wa->disableScript('asset.name')or$wa->disableStyle('asset.name')after the asset has been enabled, for example near the end of your template’sindex.php. - You want your own version of a stylesheet. Register a new asset under the same name in your template’s
joomla.asset.json. The template file is read after the system and component files, so your definition wins.
To find an asset’s name, open the joomla.asset.json that defines it (for system assets, look in media/system/joomla.asset.json and media/vendor/joomla.asset.json). Be careful when disabling assets other scripts depend on: disabling core or a form validation script will break forms and admin-style features on the front end.
Web Asset Manager vs the old methods

| Approach | Load order | Duplicates | Survives template updates | Status |
|---|---|---|---|---|
Hard-coded tags in index.php |
Manual | Possible | No | Avoid |
HTMLHelper::_('stylesheet', ...) / addStyleSheet() |
Limited | Possible | Depends | Legacy |
user.css / user.js in a child template |
Automatic | No | Yes | Recommended for small changes |
Assets in joomla.asset.json |
Automatic, by dependency | No | Yes (in your own template) | Recommended for templates and extensions |
Common mistakes and how to fix them
My file does not load at all
- Check the folder. Template assets live in
media/templates/site/{template}/cssor/js, not intemplates/{template}/css. - Check that something calls
useStyle(),useScript()orusePreset(). A registered asset that is never used prints nothing. - If you use a child template, check that the child template style is the one assigned to the page.
Every asset from my template disappeared
Your joomla.asset.json is no longer valid JSON. Look for a trailing comma, a missing quote or a missing bracket. Paste the file into any JSON validator, fix it and reload.
My CSS loads but does not win
Make the style depend on template.active so it prints after the template stylesheet. If it still loses, the problem is selector specificity rather than load order; our guide on why custom CSS isn’t working explains how to diagnose that.
Visitors still see the old file
Use 'version' => 'auto' when registering, clear Joomla’s cache under System → Clear Cache, and purge any CDN or server cache in front of the site.
Using it with a ready-made template
If you run a commercial or free template rather than Cassiopeia, the same rules apply: look for the template’s own joomla.asset.json, check whether it already supports a user.css file, and put custom code in a child template or the file the developer provides for it rather than editing core template files. Templates such as GT Ayurveda for health and wellness businesses or the one-page LT Bank Onepage give you a finished design to start from, and the Web Asset Manager is how you add your own touches on top without losing them at the next update. You can browse more in our Joomla templates collection.
Wrapping up
For a quick style tweak, create user.css in a child template’s media folder and you are done. For anything bigger, register named assets in joomla.asset.json, set their dependencies, and switch them on only where they are needed. Your files load once, in the right order, and survive updates. If you are planning a move to the current major version, read what’s new in Joomla 6 and how to upgrade next. The official Web Asset Manager documentation and Cassiopeia’s own joomla.asset.json are the best references as you go further.
- Joomla Web Asset Manager: Add CSS and JS the Right Way - October 7, 2026
- Best WordPress Plugins Every New Website Needs. - August 31, 2026
- Why Your Custom CSS Isn’t Working: Understanding CSS Specificity in WordPress and Joomla Themes - August 28, 2026








Recent Comments