I build and maintain Joomla sites. Earlier this year I took on the migration of a members' organisation's WordPress site: old hosting, an ageing PHP version, and a custom theme written in 2012 that nobody had touched in a decade. It is live now, on current WordPress and PHP, and the whole thing took days rather than weeks.
TL;DR – What surprised me was where the difficulty actually sat. I was never unclear about what I wanted. I could describe every outcome precisely. What I could not do was name the thing I was looking for, or guess where WordPress had put it. That gap turned out to be the whole job.
Contents
- The gap was vocabulary, not skill
- Four preconceptions that were simply wrong
- 1. That the plugin list shows you what is running
- 2. That "clear the cache" means one cache
- 3. That configuration lives in a file
- 4. That a contact form stores its submissions
- Three lessons that had nothing to do with WordPress
- The documented fix that fixed nothing
- Count the right thing
- Render it, do not reason about it
- Where it landed
- What I would tell another Joomla developer
The gap was vocabulary, not skill
Twenty years of Joomla gives you a mental model that mostly transfers. Both systems are PHP, both are database-backed, both separate content from presentation, both have an extension ecosystem of wildly varying quality. The concepts carry across almost perfectly. The names do not.
Most of my early time went on translation. This table is the cheat sheet I wish I had started with:
| What I wanted | Joomla | WordPress |
|---|---|---|
| Change a template file without losing it on update | Template override in html/ |
Child theme |
| Code that always runs and cannot be switched off | System plugin | Must-use plugin (mu-plugins/) |
| The site's configuration | configuration.php |
wp-config.php and rows in the options table |
| Put a form inside an article | Module position or {loadmodule} |
A shortcode typed into the post content |
| Find where a module is used | Query the modules table | Serialised PHP inside a single options row |
| Do anything at all from the command line | Limited | WP-CLI |
WP-CLI deserves its own paragraph. It is the single biggest thing WordPress has that Joomla does not, and it changed how I worked within about an hour of finding it. Being able to ask the live site what it thinks is true - what is active, what a setting actually contains, which content matches a pattern - collapsed most of my "where on earth is that" problems into one-line questions. If you are coming from Joomla, learn it first, before you learn anything else.
Four preconceptions that were simply wrong
1. That the plugin list shows you what is running
It does not. Must-use plugins load automatically and cannot be deactivated from the admin. Drop-ins - a persistent object cache, for instance - are single files that WordPress picks up by filename alone. Both were present, both were doing real work, and neither appears in the list you would naturally check. In Joomla the extension manager is the authoritative inventory. In WordPress there is no single screen that tells you everything executing on a request.
2. That "clear the cache" means one cache
The site sat behind a caching layer that maintained two distinct things: a page cache, and a separate store of optimised CSS and JavaScript. Purging the first does not rebuild the second, and no command-line option rebuilt it either. I lost real time to changes that looked like they had not applied, when in fact they had - I was reading a stale artefact from a cache I did not know existed. Coming from Joomla, where clearing the cache is one action with one meaning, this cost me more than I would like to admit.
3. That configuration lives in a file
Joomla's configuration.php is one file, human-readable, authoritative. WordPress splits its truth between a config file and a database table, and plugins add their own rows on top. At one point the command line reported a setting as off while the admin screen showed it on. Neither was lying exactly; they were reading different things. The tiebreak was the rendered page - what the server actually sent to a browser. Now, whenever a control panel and a config store disagree, I go and look at the output. It is the only source that cannot be out of date.
4. That a contact form stores its submissions
The organisation wanted the contact form retired on the grounds that it collected personal data. Reasonable. But when I checked, the form stored nothing at all - no add-on to retain entries, no tables. It composed an email and forgot. There was no personal data at rest to remove.
We retired it anyway, and it was still the right call - it removed the risk that some future plugin update quietly starts retaining submissions, and it closed a spam surface that had no filtering behind it. But the stated reason and the real reason were different, and if I had not checked I would have reported "personal data removed" to a board. That would have been false, and comfortably so.
Three lessons that had nothing to do with WordPress
The documented fix that fixed nothing
I inherited a handover note listing one outstanding bug, with a confident diagnosis and a prescribed fix: change a specific caching setting. I very nearly just did it.
I checked. Every claim in the diagnosis failed against the live site, and the feature worked correctly when tested as an ordinary visitor. Somebody had formed a plausible theory, written it down, and moved on; and because it was written down it had quietly become fact. Had I followed the instruction I would have changed a setting that was already in the desired state, triggered an unnecessary rewrite of the server config, and reported a fix for a bug that did not exist.
The same note contained a verification command that returned a false positive - it matched a phrase inside its own explanatory comment. A check that cries wolf is worse than no check, because the next person learns to ignore it.
Count the right thing
The last task was aligning the footer of a large static archive site - roughly nineteen thousand HTML files - with the newly updated one. I wrote a script, tested it against real pages, and ran it in dry-run mode. It reported that every file with a footer would be changed. That looked conclusive.
It was not. I had the script report a count per rule rather than per file, and the totals did not reconcile. One rule fired forty-four times fewer than the others; another fired exactly once fewer, out of 18,928. Both discrepancies were real. There were three footer variants on that site, not one, and my patterns only matched the first. The file count looked perfect the entire time.
Finding the single odd page out of nearly nineteen thousand took one query, because arithmetic pointed straight at it. Without the per-rule breakdown I would have shipped a site with two stale icons on one page and never known.
If a total can be right while the details are wrong, you are counting the wrong thing.
Render it, do not reason about it
Two icons had no image file, so I substituted inline SVG at what the markup said the size was: 24 pixels. Correct by inspection, and wrong in reality. A stylesheet carried a blanket rule that overrode the width and height attributes on images, so every existing icon actually rendered at 48 pixels. My replacements would have sat at half the size of everything beside them - and because the rule targeted images only, it did not apply to my SVG.
No amount of reading the markup would have caught that. Loading the page did, in seconds.
Where it landed
Live on current WordPress and PHP, on new hosting, with backups running and a restore actually tested rather than assumed. A separate legacy archive alongside it, aligned and consistent. Days, not weeks.
I should be straight about how it went that fast: I did the work alongside an AI assistant, and the value was not that it wrote code for me. It was that it closed the vocabulary gap instantly. "What is Joomla's system plugin called here?" is a five-second question with an assistant and a forty-minute one with a search engine, because you cannot search for a term you do not know. Just as usefully, it kept insisting on checking things I would have taken on trust - the inherited bug report, the assumption about stored data, the file count that looked clean. Most of the findings above came out of verification I would probably have skipped under time pressure.
What I would tell another Joomla developer
- Learn WP-CLI on day one. Everything else gets easier afterwards.
- Your concepts transfer; your vocabulary does not. Budget time for translation, and write down the mapping as you go.
- Do not trust an inherited diagnosis. Reproduce the fault yourself before applying a documented fix. A written-down theory becomes accepted fact remarkably fast.
- Verify the premise, not just the request. "Remove this because it stores personal data" deserves ten minutes confirming that it does.
- Make your checks specific enough to fail. A verification that cannot distinguish success from failure is decoration.
- Look at the rendered output when tools disagree. It is the only thing that cannot be stale.
- Joomla is better. But we are working at the pleasure of the client and they made this choice and its the same expertise really, just a different vocabulary.
None of that is WordPress-specific, which is rather the point. The platform was the unfamiliar part. The mistakes available to me were the same ones available anywhere. I still prefer Joomla and would choose it above Wordpress in a heartbeat but it was not my decision to make here and is far beyond the scope of modernisation, security updates and new hosting platform.