026 —Joomla
Joomla 3 to Joomla 5: template overrides that survive
Most Joomla 3 overrides are sixty percent salvageable. The other forty percent will silently break your site. Here is the rescue order we use on real client sites.
A Dutch agency we work with sent over an FTP credential on a Friday evening: 38 templates, one custom Joomla 3.10 site for a regional events org, due to be on PHP 8.2 inside the month. Their lead developer had already tried the Joomla Update Component once. It had silently flattened half their article overrides and turned the homepage hero into a vertical stack of grey buttons. The migration from Joomla 3 to Joomla 5 is not the kind of jump where you click update and walk away.
This is the playbook we now run on every Joomla 3 to Joomla 5 port. Some of it is boring (a literal grep through your override tree). Some of it is non-obvious (the Web Asset Manager will not warn you when an old script tag silently no-ops). All of it has bitten us in the last twelve months.
The audit pass before you touch anything
Before you upgrade a single file, list every override in the current site. In Joomla 3 these live in two places under your template:
find templates/YOUR_TEMPLATE/html -type f -name "*.php" | sort
find templates/YOUR_TEMPLATE/html/layouts -type f -name "*.php" | sort
For a five-year-old site you will usually get 20 to 60 files. Group them by component: com_content, com_users, com_contact, plus module overrides under mod_* and any shared layouts under html/layouts/joomla/.
Now diff each override against the Joomla 3 core file it came from. The output of that diff is the only thing that matters during the migration. If an override is ninety-five percent identical to core, you almost certainly do not need to port it. Joomla 5 ships better defaults for article display, pagination, and form rendering than Joomla 3 did. The override exists because somebody, five years ago, needed to add one CSS class.
Tag each Joomla 3 override into one of three buckets:
- Keep. Substantive layout or markup changes that the client still depends on.
- Reduce. Mostly core code with one or two custom tweaks. These become small layout overrides in Joomla 5, not full copies.
- Drop. Overrides whose only purpose was to add a Bootstrap 2 utility class. Joomla 5 has already moved on.
On the 38-template legacy site above, 24 overrides ended up in drop once we lined them up against current core. Less code to migrate is less code to break.
Bootstrap 2 to Bootstrap 5: the class swap
This is the single biggest source of visual regressions when porting Joomla 3 overrides. Joomla 3 shipped Bootstrap 2.3, Joomla 4 jumped to Bootstrap 5, and Joomla 5 stayed there. If your overrides contain Bootstrap utility classes, most of them have new names or no longer exist at all.
The renames you will hit in almost every Joomla 3 override:
pull-left float-start
pull-right float-end
btn-default btn-secondary
btn-xs btn-sm (xs removed)
panel card
panel-heading card-header
panel-body card-body
hidden-phone d-none d-md-block
visible-phone d-block d-md-none
img-responsive img-fluid
input-large form-control-lg
control-group mb-3
help-block form-text
row-fluid row
span6 col-md-6
span12 col-12
Run a bulk replace on a copy of the override tree first, then eyeball each file. Bootstrap's official migration guide covers the full diff between versions; bookmark it because you will be back. Pay particular attention to anything wrapped in row-fluid, which is gone, and to span6 column classes that used to assume a twelve-column fluid grid.
The non-obvious one: btn-group still exists, but the way it composes with dropdowns has changed. If your override has a dropdown toggle inside an article action bar, expect to rewrite the markup, not just rename classes. The data attribute is now data-bs-toggle rather than data-toggle, and the JavaScript glue is no longer jQuery.
JHtml is dead, long live HTMLHelper
Every Joomla 3 override that calls into the framework uses the unprefixed legacy class names: JHtml, JFactory, JText, JLayoutHelper, JRoute. None of these exist by default in Joomla 5. They were soft-deprecated in 4.0 and removed in 5.0.
The replacements live under namespaced classes, and your overrides need use statements at the top of each file:
<?php
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Router\Route;
$app = Factory::getApplication();
$user = $app->getIdentity();
echo HTMLHelper::_('image', 'system/icon.png', Text::_('JTOOLBAR_BACK'));
echo LayoutHelper::render('joomla.content.icons', ['item' => $this->item]);
A search-and-replace will get you seventy percent of the way. The remaining thirty percent is the calls that changed signature, not just name. The ones to watch on every Joomla 3 to Joomla 5 port:
JFactory::getUser()becomesFactory::getApplication()->getIdentity(). The old method is still callable in some contexts but logs a deprecation that will spam your error log on every page render.JFactory::getDbo()still works through the legacy facade, but new code should pullFactory::getContainer()->get('DatabaseDriver').JText::_()becomesText::_(). Cheap to migrate, easy to miss in includes and partials.JRoute::_()becomesRoute::_()with the same signature.
If you are working from an article override and you call $this->escape(), that still works. View methods on the inherited HtmlView class did not change, which means most loops over $this->items port unchanged.
Loading scripts through the Web Asset Manager
This is where most Joomla 3 to Joomla 5 ports break silently. In Joomla 3 you loaded a script in an override by writing:
$doc = JFactory::getDocument();
$doc->addScript('templates/your_template/js/article-gallery.js');
$doc->addStyleSheet('templates/your_template/css/article-gallery.css');
In Joomla 5 that code still executes. It will not throw an error. It will not log a deprecation. It will just refuse to put your asset anywhere useful, because the Web Asset Manager has taken over and expects assets to be registered, not appended.
The correct pattern is to declare your asset in templates/your_template/joomla.asset.json, then call it from the override:
{
"$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json",
"name": "your_template",
"version": "1.0.0",
"assets": [
{
"name": "template.article-gallery",
"type": "script",
"uri": "js/article-gallery.js",
"dependencies": ["core"],
"attributes": { "defer": true }
},
{
"name": "template.article-gallery",
"type": "style",
"uri": "css/article-gallery.css"
}
]
}
<?php
use Joomla\CMS\Factory;
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->useScript('template.article-gallery')
->useStyle('template.article-gallery');
The benefit beyond surviving the upgrade: declared assets have versioning, dependency resolution, and deferred loading for free. If your script depends on core or on bootstrap.dropdown, you declare it once and stop fighting load order.
Form fields and the layouts directory
Override files for form fields and shared layouts moved between major versions. In Joomla 3, layout overrides lived under templates/your_template/html/layouts/joomla/. That path still works in Joomla 5, but the underlying layout files have been substantially rewritten, particularly anything under joomla.form. and joomla.content..
A common failure case: an agency override for the article info block at html/layouts/joomla/content/info_block.php. In Joomla 3 the file was around sixty lines. In Joomla 5 the core file is closer to two hundred and uses Bootstrap 5 markup, semantic icons via HTMLHelper::_('icon', ...), and a different data shape. Copy-pasting the old override on top of the new core will appear to work, then quietly hide the publish date and author link on every article.
The pattern that survives the jump: start from the current Joomla 5 core layout, apply your customisations to that file, save it as the override. Never carry the old override file forward unmodified. Treat each layout override as a small patch on top of current core, not as a fork frozen in 2019.
A test pass that catches the silent breakage
Joomla migrations fail in places where nothing throws. The test plan we run on every Joomla 3 to Joomla 5 site:
- Set
error_reporting = E_ALLinconfiguration.phpand tail the PHP error log while you click through. Deprecation warnings will tell you which override is still callingJFactory. - Open every menu item type the site uses: article single, category blog, contact form, login, registration, search. Each has at least one shared layout that could be silently inherited.
- Submit one of each form on the front end. Form fields are where the layout override path bites hardest.
- Diff the homepage HTML against a Joomla 3 snapshot.
curl -s https://old-site.com/ | tidy -q -i > old.htmland the equivalent on the staging copy. Mass class renames will jump out of the diff. - Run Lighthouse on three representative pages. Joomla 5 default markup is leaner; if your score went down, an old override is still emitting Bootstrap 2 columns inside Bootstrap 5 containers.
The full audit will take a day on a mid-sized site. The cost of skipping it is a Tuesday-morning phone call from a client whose registration form has been quietly broken since Friday's deploy.
When we built Pier we ran into this exact thing on the 38-template site: the override audit was the only honest way to know what would break, but doing it by hand was punishing. The way we ended up handling it was to diff every legacy file against its upstream Joomla counterpart before any edit, with full version history on each step so the audit and the actual port live in the same place.
The smallest thing you can do today: run that find command against your own template's html/ directory and sort the output by file size. The biggest files are almost always the most expensive overrides, and the most expensive overrides are almost always the ones you no longer need.
— Questions —
Can I run Joomla 3 template overrides directly on Joomla 5?
Some will load without errors, but most will silently miscompose Bootstrap markup or hit removed namespaces. Plan to port every override you intend to keep.
Does the Web Asset Manager fully replace addScript?
addScript still exists in Joomla 5 and will not throw, but assets added that way bypass dependency resolution and versioning. Use joomla.asset.json for anything new.
How long does a Joomla 3 to Joomla 5 override migration take?
For a 30-override mid-sized site, a full audit, port, and test pass is typically two to three working days. Sites with custom forms or shop overrides skew longer.
Do I still need a child template for overrides in Joomla 5?
Child templates are supported and recommended when you want a clean upgrade path for the parent. For a fully custom template they remain optional.