We installed our first Joomla 1.0 from a zip file, over FTP, on shared hosting with cPanel. Joomla turned 20 in 2025, and in those twenty years we've built, broken, migrated and rebuilt more Joomla sites than we can count. This post is the short history, the projects we're proud of, and the lessons that changed how we write code. With the code, because the lessons only mean something with the code.
Twenty years in six versions
- 2005 – Mambo becomes Joomla. We didn't follow the politics. We saw a new name, a community on fire, and the forums where we learned.
- 2008 – Joomla 1.5. MVC,
JFactory, template overrides. Confusing at first, then the moment we realised Joomla was a framework, not just a site builder. - 2012 – Joomla 2.5. Rock solid and quirky. The golden age of VirtueMart shops and K2 blogs. Some of the 2.5 sites we migrated still run today, two major versions later.
- 2012–2021 – Joomla 3. Bootstrap 2, template frameworks, a huge extension ecosystem, and our first complex systems: surveys, ticketing, streaming.
- 2021 – Joomla 4. The rewrite: namespaces, a DI container, Bootstrap 5, the Web Asset Manager, an API. It took long and it was worth it.
- 2023–2026 – Joomla 5 and 6. The calm after the rewrite: smooth updates, PHP 8.3, automated updates, child templates. Our own site runs Joomla 6.2, and we're testing extensions against the 7.0 branch.
Projects that last
The best part of twenty years is watching sites outlive the version they were built on. A few we've built, migrated or rebuilt:
- Vivi City Network: 11 travel sites migrated into one multisite Joomla platform.
- Vivi City: the global travel comparison platform, redesigned and tuned for speed.
- Multifamily Insiders, the largest online community for multifamily professionals, and its sister sites moved to Joomla 6.
- MBCCS – Marina Bay Cruise Centre Singapore: website and customer portal.
- IGAD: a multilingual information portal for an intergovernmental organisation.
- NorCal CarciNET and WARMTH: CiviCRM sites on Joomla 6, before CiviCRM officially supports it.
- Therapeeds: straight from Joomla 3 to a rebuilt Joomla 6 site.
The full list is on Case studies.
Seven lessons, with the code
1. Never edit core. Listen to events instead
Every core edit we've ever inherited was overwritten by an update or blocked one. Joomla is event-driven: almost anything core does can be changed from a plugin. The modern form, unchanged since Joomla 4, is a subscriber that declares its events:
// plugins/content/readingtime/src/Extension/ReadingTime.php
final class ReadingTime extends CMSPlugin implements SubscriberInterface
{
public static function getSubscribedEvents(): array
{
return ['onContentPrepare' => 'addReadingTime'];
}
public function addReadingTime(ContentPrepareEvent $event): void
{
if ($event->getContext() !== 'com_content.article') {
return;
}
$item = $event->getItem();
$item->readingTime = (int) ceil(str_word_count(strip_tags($item->text)) / 220);
}
}
2. Overrides should be surgical, and in a child template
An override is a copy of core that stops getting core's fixes. We override only what we must, in a child template, and after every update we check System → Templates for "Changes found" and compare. On this site alone, Joomla 6.2's new image classes meant re-checking the image layout overrides.
3. URLs are a contract
Menu structure is URL structure in Joomla, and every reorganisation, migration or "Remove IDs from URLs" switch can break links that took years to earn. Before any migration we crawl the old sitemap, and afterwards we check that every URL returns 200 or a deliberate 301. Most lost rankings we've been asked to recover came from skipping that one step.
4. Modules are small applications
Since Joomla 4, a module has a dispatcher and helpers created by a helper factory, which injects the database. We build them like that, even small ones. They're testable, cacheable and survive major versions:
// modules/mod_example/src/Helper/ExampleHelper.php
class ExampleHelper implements DatabaseAwareInterface
{
use DatabaseAwareTrait;
public function getItems(Registry $params): array
{
$db = $this->getDatabase(); // injected, not Factory::getDbo()
$query = $db->createQuery()->select('*')->from($db->quoteName('#__example'))->setLimit(5);
return $db->setQuery($query)->loadObjectList();
}
}
5. The query builder is not optional
Prepared statements through Joomla's query builder (bind(), whereIn()) are what stand between a site and SQL injection, and they keep queries portable between MySQL, MariaDB and PostgreSQL. Every SQL injection we've cleaned up was string concatenation in an extension.
6. Custom fields go further than they look
We've built pricing tables, survey logic and content variations on core custom fields plus layout overrides, where we'd once have written a component. With Joomla 6's number and note fields and Joomla 6.1's media fields, the list keeps growing. Fewer custom components means fewer things to port at the next major version.
7. Extensions live or die by updateability
Before an extension goes into a client site, we check: namespaced code, an update server, a release in the last year, and no deprecation warnings on the current Joomla version. That last check has become our best predictor of whether the vendor will be ready for the next major version. On Joomla 6, it's a one-line setting: Log Deprecated API.
Why we still choose Joomla
- Overrides at every level (layouts, language strings, templates, plugins) to adapt it without forking it.
- A real framework underneath: DI container, console commands, a REST API and a task scheduler, all in core.
- ACL that handles real organisations, with view levels and permissions per group, per category, per item.
- Multilingual in core, with associations and hreflang, no plugin required.
- A predictable lifecycle: a major version every two years, each with a compatibility plugin and a clear deprecation list, so upgrades can be planned.
Twenty years in, Joomla is still the tool we reach for when a site has to do more than publish pages, and still be maintainable in five years.
Need someone who's been with Joomla since day one? Migrations, rebuilds, custom extensions and long-term maintenance, by developers who've done it for twenty years.
Let's talk →