Moodle's architecture from 1.0 to 5.3, and what it broke
Moodle has been rebuilt in place three times since 2002. What each rebuild changed, from the 2.0 rewrite to hooks, DI and React, and what plugins had to rewrite.
Moodle has never been thrown away and rewritten from scratch. It has been rebuilt in place three times. The 2.0 release in 2010 replaced the file, database and output layers. Between 2.6 and 3.8 Moodle added the APIs most plugins still use: events, logging, tasks, AMD JavaScript, Mustache templates and the Privacy API. Since 4.3 it has been moving to hooks, dependency injection, routing, Composer, a /public web root and React.
Each wave broke plugins in its own way. If you own a plugin, the version it was written for tells you which waves it missed.
The timeline in one table
Dates are from Moodle’s releases page.1 Changes are from each version’s release notes.
| Version | Released | Architecture change | What plugin authors had to do |
|---|---|---|---|
| 1.0 | 20 Aug 2002 | First release | |
| 1.7 | 7 Nov 2006 | Roles and capabilities, XMLDB | Move permission checks to capabilities, define tables in XMLDB |
| 2.0 | 24 Nov 2010 | New file handling, database layer, rendering layer | Port the plugin, more or less |
| 2.4 | 3 Dec 2012 | MUC caching, PHPUnit replaces SimpleTest | Move tests, optionally use caches |
| 2.6 | 18 Nov 2013 | New events system, class autoloader | Write event classes, move code into classes/ |
| 2.7 | 12 May 2014 | Log stores, Task API, add_to_log() deprecated | Convert logging to events, cron to tasks |
| 2.9 | 11 May 2015 | AMD JavaScript, Mustache templates | Move away from YUI and PHP-built HTML |
| 3.2 | 5 Dec 2016 | Boost theme on Bootstrap 4 | Fix markup and navigation for Boost |
| 3.5 | 17 May 2018 | Privacy API | Add a privacy provider to every plugin |
| 3.8 | 18 Nov 2019 | Babel, ES modules, subplugins.json | Build JavaScript with Grunt |
| 4.0 | 19 Apr 2022 | New navigation, course index, core_courseformat | Fix course formats and navigation callbacks |
| 4.3 | 9 Oct 2023 | Hooks API, legacy cron removed | Start moving lib.php callbacks to hooks |
| 4.4 | 22 Apr 2024 | Dependency injection container | Optional for now |
| 4.5 | 7 Oct 2024 | Routing, AI subsystem | Optional for now |
| 5.0 | 14 Apr 2025 | Bootstrap 5, Atto and subplugins.php removed | Rebuild Atto plugins, check templates |
| 5.1 | 6 Oct 2025 | /public web root, routing engine | Move plugin folders, fix paths |
| 5.2 | 20 Apr 2026 | React, Composer, deprecations up to 4.4 removed | Replace removed functions |
| 5.3 | 5 Oct 2026 | Symfony Console, DI by attributes, Classic theme removed | Rebuild Classic-based themes |
What did Moodle 1.x look like?
Moodle 1.0 shipped on 20 August 2002. It was procedural PHP: pages that included config.php, global objects like $CFG and $USER, and plugins that were folders of scripts with a lib.php full of functions core would call by name. That lib.php callback pattern is the one Moodle is still replacing in 2026.
The most important 1.x release for developers was 1.7 in November 2006. Its release notes introduced “permissions based on fine-grained capabilities” that could be assigned in all contexts, and XMLDB, so that “developers now have just one XML file to edit when changing the database structure”.2 Roles and capabilities are still how every plugin checks permissions today. install.xml is still how every plugin defines its tables. Both were right, and both have survived twenty years almost unchanged. That is rare in any codebase.
Why was 2.0 a rewrite?
Look at the dates. Moodle 1.9 came out in March 2008. Moodle 2.0 came out on 24 November 2010. More than two and a half years without a major release, the longest gap in Moodle’s history.
The 2.0 release notes tell you why.3 File handling changed and “you will need to update your code”. The database layer changed, with the same warning. The rendering layer changed and was “mostly backwards compatible, but you are advised to upgrade your code”. Themes could now change the HTML of a page. Every page had to call require_login explicitly. Navigation and Settings blocks appeared on every page, web services were exposed across the codebase, and the JavaScript moved onto a YUI-based framework. Developers were pointed to a page called “Migrating contrib code to 2.0”.
For plugin authors this was not an upgrade, it was a port. Files now sat behind the File API and inherited the permissions of the activity that used them. Any plugin that touched files, built its own HTML or used the old database functions had to be reworked, and an unmaintained 1.9 plugin simply did not come along.
Was it the right call? Yes. The File API and the renderer system are still the base of Moodle 5.3. The mistake was doing it as one giant release instead of in steps, which is exactly the lesson Moodle seems to have learned for the 4.x and 5.x changes.
How did 2.4 to 3.8 change the way plugins are written?
After 2.0, Moodle stopped doing big bangs and started adding APIs one release at a time. Most plugins written between 2014 and 2022 are built on what arrived in this stretch.
- 2.4 added the Moodle Universal Cache, replaced SimpleTest with PHPUnit and added Composer support (MDL-36701).4
- 2.6 added a “New events infrastructure” (MDL-39797) and an “Automatic class loader” (MDL-39854).5 Events became classes, and plugin code could live in a
classes/folder with namespaces instead of being included by hand. - 2.7 finished the job. It added a logging subsystem with log store plugins, deprecated
add_to_log(), and told developers that “all plugins should convert their logging and triggering of events to the new API”. Cron was rebuilt around scheduled and ad hoc tasks.6 - 2.9 added AMD JavaScript modules and “support for implementing renderers as mustache templates (php and javascript)”.7 Web service functions could be called straight from JavaScript.
- 3.2 shipped the Boost theme on Bootstrap 4.8
- 3.8 moved JavaScript to Babel. Moodle’s docs say AMD was the module format from 2.9 to 3.8, and that since 3.8 ES modules are supported and transpiled to AMD by Babel.9 The same release moved subplugin definitions to JSON.10
This period is where Moodle got its plugin API right. Events, tasks, templates and autoloaded classes are a decent design, and a plugin written well in 2016 still mostly works in 5.3. The weak spot was the legacy paths. Old-style event handlers, add_to_log(), legacy cron and subplugins.php all kept working for years, so many plugins never moved. Legacy cron was only removed in 4.3, nine years after the Task API.11 subplugins.php went in 5.0, more than five years after its replacement.12
What did GDPR force?
The Privacy API in 3.5 (May 2018) is the one change in this list that came from outside. The release notes are blunt: “All plugins must implement Privacy API to be compliant with GDPR requirements,” which means reporting on, exporting and deleting the user data they store (MDL-61307).13
It was the right thing to build and it was built under a deadline, which shows. It was the first time every plugin, including ones that stored nothing, had to add a class to stay correct. Plugins that store user data and still have no provider are silently skipped by data requests. We covered that in what breaks when you upgrade Moodle. Nothing warns you, so it is an easy gap to miss.
How did the frontend move?
Slowly, and in layers that pile up in old plugins:
- YUI from 2.0, as part of the new output system.
- AMD modules from 2.9, with jQuery alongside.
- ES module syntax from 3.8, compiled back to AMD.
- React, TypeScript and real ES modules from 5.2, with a
{{#react}}Mustache helper and import maps.1415 - In 5.3, a PHP API for mounting React components and a conversion of heavily used AMD modules to ESM.16
An old plugin that was patched over the years can mix several of these generations in one folder. React is where this stops being optional for new work: Moodle’s frontend guide now says to avoid introducing new AMD based patterns.15 Our post on React in Moodle core covers what that means for existing plugins.
Our view: React was late. Moodle was building interactive screens like the 4.0 course index on its own JavaScript modules while most of the web had settled on component frameworks years earlier. Doing it now, with a design system and tokens behind it, is still the right decision. It would have been cheaper in 2019.
What changed after 4.0?
Moodle 4.0 (April 2022) was mostly a user interface release: primary, secondary and tertiary navigation, the course index, and a new core_courseformat subsystem with activity UI moved to output classes and templates.17 Custom course formats felt it most.
The deeper change started with 4.3. Since then, core has been replacing its oldest internal patterns with standard PHP ones:
- Hooks, 4.3. Built on PSR-14. Plugins register callbacks in
db/hooks.php, and hooks are a direct replacement for one-to-manylib.phpcallbacks. When a plugin has both, thelib.phpcallback is ignored.18 - Dependency injection, 4.4. A PSR-11 container that wraps PHP-DI, used through
\core\di::get()or, better, constructor injection.19 - Routing, 4.5. Based on Slim and FastRoute. Plugins define routes as classes in their
routenamespace with a\core\router\routeattribute.20 5.0 added routing for regular pages, and 5.1 added a routing engine with a compatibility layer for old URLs.1221 - AI subsystem, 4.5. Provider plugins and placements, with OpenAI and Azure providers at launch (MDL-82627).22 5.0 added an Ollama provider and AI usage reports.
- The
/publicweb root, 5.1. Web-accessible files moved intopublic/(MDL-83424), and plugins installed through git had to be moved by hand.21 - Composer, 5.2. Third-party libraries now come through Composer (MDL-87950), and the same release removed deprecated methods up to Moodle 4.4.14
- Symfony Console and attribute injection, 5.3. CLI commands can use Symfony Console (MDL-85847), and the container supports injection through PHP attributes (MDL-89528).16
Put together, a Moodle 5.3 plugin can look a lot like code from any modern PHP framework: namespaced classes, injected services, routes declared with attributes, hooks instead of magic function names, a React component for the interactive parts. A plugin from 2012 runs next to it on the same site. Moodle has kept that working, and it is both its biggest strength and the reason upgrades keep surprising people.
Which changes were right, and which were late?
Right and on time: roles and capabilities (1.7), XMLDB (1.7), the File API (2.0), events and tasks (2.6 and 2.7), Mustache (2.9). These are the parts of the architecture nobody argues about.
Right but late: hooks and dependency injection. The lib.php callback model dates from the early versions and only began to be replaced in 2023. PHP frameworks had event dispatchers and service containers for about a decade before Moodle adopted them for its core extension points. React fits here too.
Right but forced: the Privacy API. A law made it happen, not a design plan.
The pattern that cost the most: long deprecation tails. Moodle keeps old APIs alive for many releases, which is kind to site owners in the short term. But it means a plugin can ignore warnings for years and then break on one release, like 5.0 (Atto, subplugins.php) or 5.2 (deprecations up to 4.4 removed). The 2.0 rewrite was one big break. The 5.x line is a series of smaller ones, each easy alone, painful when a plugin missed several.
Where is Moodle going?
The direction since 4.3 is consistent: fewer Moodle-only mechanisms, more standard PHP and JavaScript. PSR interfaces for events, containers and HTTP. Slim, PHP-DI, Symfony Console and Composer instead of home-grown code. React and TypeScript instead of a custom module system. Removing Classic in 5.3 and Atto in 5.0 shows HQ is now willing to delete things, not just deprecate them.1612
For developers that is good news over time. Skills from the wider PHP and JavaScript world now carry over into Moodle. The cost is that every LTS cycle removes more of what older plugins depend on, so the gap between a maintained plugin and an abandoned one grows faster than it used to. The server side moves too: Moodle 5.3 needs PHP 8.3, see Moodle 5 requirements.
What does this mean if you own old plugins?
Find the Moodle version each custom plugin was written for and read the table above from that row down. That list is roughly your rewrite list:
- Written for 2.x: probably still uses legacy logging or events, YUI or early AMD, and no privacy provider. Often cheaper to rebuild on the same database tables.
- Written for 3.x: usually sound underneath. Expect removed deprecated functions, Bootstrap 4 markup, AMD JavaScript and
lib.phpcallbacks that now have hooks. - Written for 4.x: mostly the
/publicmove, Bootstrap 5 and the 5.2 removals. - Any age: if it is an Atto plugin or a Classic-based theme, it has to be rebuilt, not fixed.
None of this needs to happen all at once. It does need to happen before the upgrade that removes the API your plugin depends on, not during it. DevLMS builds new Moodle plugins and updates old ones for current versions.
Sources
-
MoodleDocs, Moodle 1.7 release notes. ↑
-
MoodleDocs, Moodle 2.0 release notes. ↑
-
MoodleDocs, Moodle 2.4 release notes. ↑
-
MoodleDocs, Moodle 2.6 release notes. ↑
-
MoodleDocs, Moodle 2.7 release notes. ↑
-
MoodleDocs, Moodle 2.9 release notes. ↑
-
MoodleDocs, Moodle 3.2 release notes. ↑
-
Moodle HQ, JavaScript Modules. ↑
-
MoodleDocs, Moodle 3.8 release notes. ↑
-
Moodle HQ, Moodle 4.3 release notes. ↑
-
Moodle HQ, Moodle 5.0 release notes. ↑ ↑ ↑
-
MoodleDocs, Moodle 3.5 release notes. ↑
-
Moodle HQ, Moodle 5.2 release notes. ↑ ↑
-
Moodle HQ, Frontend development guide (5.2). ↑ ↑
-
Moodle HQ, Moodle 5.3 release notes. ↑ ↑ ↑
-
Moodle HQ, Moodle 4.0 release notes. ↑
-
Moodle HQ, Dependency Injection. ↑
-
Moodle HQ, Moodle 5.1 release notes. ↑ ↑
-
Moodle HQ, Moodle 4.5 release notes. ↑
Need a Moodle plugin built or rescued?
Tell the scoping assistant what you need. You get a fixed price and a timeline in a few minutes, and an engineer reviews it within one business day.
Get a fixed quote