This is the order of work we use to move a Joomla 3 or 4 site to Joomla 5.4, written as a runbook: what to run, what to check after each step, and the problems that cost us time on real migrations. The last section covers the next jump, 5.4 → 6, from the command line, the way we upgraded our own site.
Step 0: Inventory and a staging copy
Never migrate the live site. Copy it, files and database, to a staging host with the target PHP version, and write down what you are starting from:
# terminal – on the copy
grep -E "MAJOR_VERSION|MINOR_VERSION|PATCH_VERSION|RELEASE|DEV_LEVEL" libraries/src/Version.php
php -v
mysql -e "SELECT type, element, folder, enabled,
JSON_UNQUOTE(JSON_EXTRACT(manifest_cache, '$.version')) AS version
FROM jos_extensions
WHERE JSON_UNQUOTE(JSON_EXTRACT(manifest_cache, '$.author')) NOT LIKE 'Joomla%'
ORDER BY type, element;" mydb > extensions-before.txt
For every line in extensions-before.txt, decide: update (a Joomla 5 version exists), replace (abandoned, an alternative exists), or remove (not used). Disabled extensions are the easiest wins. Uninstall them before you start, and the migration has fewer moving parts.
On staging, also turn off outgoing mail and put payment plugins in test mode. Scheduled tasks and cron jobs on a copy will otherwise mail real customers.
Step 1 (Joomla 3 only): 3.10 → 4.4
This is the expensive step, because Joomla 4 rewrote the core.
- Update to the last 3.10. In Joomla Update, the Pre-Update Check tab lists every extension with "compatible / not compatible / unknown" for Joomla 4, and checks PHP and database versions.
- Switch the template to Cassiopeia on staging before the update. A Joomla 3 template will not render on Joomla 4. It gets rebuilt anyway, and a working default template makes the result testable.
- Run the update to 4.4 through Joomla Update ("Upload & Update" with the 4.4 update package if the channel doesn't offer it).
- Fix the database schema: System → Maintenance → Database. Every row must say "up to date". If one doesn't, press "Update Structure" and read what it reports.
- Rebuild the template for Joomla 4/5 (Bootstrap 5, the Web Asset Manager, new layout overrides). Copying old overrides across is the most common source of broken pages after a migration.
Content, users, menus and categories come across reliably. In our experience, what breaks is everything around them: custom template overrides, editors' inline HTML that relied on Bootstrap 2 classes (span6, pull-right), and extensions that store HTML with old markup.
# terminal – find Bootstrap 2/3 classes in article HTML that will lose their styling
mysql -e "SELECT id, title FROM jos_content
WHERE introtext REGEXP 'class=\"[^\"]*(span[0-9]+|pull-(left|right)|offset[0-9]+|hidden-phone|visible-desktop)'
OR \`fulltext\` REGEXP 'class=\"[^\"]*(span[0-9]+|pull-(left|right)|offset[0-9]+)';" mydb
Step 2: 4.4 → 5.x with the compatibility plugin
From 4.4 the jump is an update, not a migration. Joomla 5 ships Behaviour – Backward Compatibility, which keeps Joomla 4 code that uses deprecated APIs running.
- Update every extension to its newest version for Joomla 4 first. Most vendors released versions that run on both 4.4 and 5.
- Check PHP 8.1+ (8.3 if you're going on to Joomla 6).
- Run Joomla Update → 5.x. Check System → Maintenance → Database again.
- With Log Deprecated API on (Global Configuration → Logging), click through the site and read
administrator/logs/deprecated.php. That file is your to-do list for Joomla 6.
Step 3: 5.x → 5.4 and leave the site there for a while
Update to the latest 5.4.x, re-test, and go live. Joomla 5.4 has security support until October 2027. A site that is cleanly on 5.4 with an empty deprecation log is in the best possible position for Joomla 6, and you've only changed one thing at a time.
Going on to Joomla 6 from the command line
We upgraded our own site from 5.4 to 6.2 with the Joomla CLI. It's faster than the browser for big sites and doesn't hit PHP time limits. The commands:
# terminal – on staging, with a backup you have restored once
php cli/joomla.php core:update:check
php cli/joomla.php core:update # latest 5.4.x first
php cli/joomla.php core:update:channel next # offer the next major version
php cli/joomla.php core:update # → 6.x
php cli/joomla.php update:joomla:remove-old-files
php cli/joomla.php maintenance:database # must report every table as up to date
php cli/joomla.php cache:clean
What we hit on the way, and what you are likely to hit too:
- The CLI itself crashed before the update started. The CLI application boots enabled plugins, and plugins whose entry file still uses
J*class names fail when they load. We disabled those plugins (in our case 112, from three commercial extensions) for the upgrade only, then re-enabled them. Joomla also asks you to disable the old Joomla 5 compatibility plugin before the major update. - The finalisation step failed after the files were replaced, because the running process still had Joomla 5 classes in memory. Running finalisation again with the new code, and then
maintenance:database, completed it cleanly. Check the schema report. "Files updated" is not "upgrade done". - Extensions that need old class names ran again once "Classes Aliases" was turned on in Behaviour – Backward Compatibility 6. It's off by default.
- Case-insensitive file systems. On macOS (our local copies), an extension update that renamed folders from
classes/toClasses/deleted the new folder together with the old one. Linux servers are fine, but test locally on a case-sensitive volume or expect surprises.
The checks we run after every step
| Check | How |
|---|---|
| Schema up to date | System → Maintenance → Database, or maintenance:database |
| No PHP errors | web server error log, with Error Reporting at "Maximum" on staging |
| Deprecations | administrator/logs/deprecated.php |
| Every page type renders | home, category blog, article, contact form, search, login, 404, every component's main views |
| Forms send | contact, registration, password reset; check the mail catcher |
| Overrides | System → Templates → "Changes found" notices |
| URLs unchanged | crawl the old sitemap against staging; every URL should be 200 or a deliberate 301 |
The last row matters more than it looks. Router and menu changes during a migration can quietly change URLs, and rankings go with them. A crawler that takes a URL list as input (Screaming Frog, or wget --spider -i urls.txt) finds them in minutes.
When it goes live
- Freeze content on live (or note what changes during the switch).
- Take a final backup of live.
- Deploy staging to live, with live's
configuration.phpvalues (database, paths, mail, live site URL). - Clear all caches, including the CDN, and check the same list again on live.
- Keep the old live backup for at least a month.
If the site runs a shop or a CRM, it has its own upgrade path on top of this one: see VirtueMart 3 to 4.8 on Joomla 5 and upgrading a Joomla site with CiviCRM.
