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.

Drawing of a building on old wooden stilts with brick floors and glass floors stacked above, while a person on scaffolding lifts a blue beam to the top floor.

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.

VersionReleasedArchitecture changeWhat plugin authors had to do
1.020 Aug 2002First release
1.77 Nov 2006Roles and capabilities, XMLDBMove permission checks to capabilities, define tables in XMLDB
2.024 Nov 2010New file handling, database layer, rendering layerPort the plugin, more or less
2.43 Dec 2012MUC caching, PHPUnit replaces SimpleTestMove tests, optionally use caches
2.618 Nov 2013New events system, class autoloaderWrite event classes, move code into classes/
2.712 May 2014Log stores, Task API, add_to_log() deprecatedConvert logging to events, cron to tasks
2.911 May 2015AMD JavaScript, Mustache templatesMove away from YUI and PHP-built HTML
3.25 Dec 2016Boost theme on Bootstrap 4Fix markup and navigation for Boost
3.517 May 2018Privacy APIAdd a privacy provider to every plugin
3.818 Nov 2019Babel, ES modules, subplugins.jsonBuild JavaScript with Grunt
4.019 Apr 2022New navigation, course index, core_courseformatFix course formats and navigation callbacks
4.39 Oct 2023Hooks API, legacy cron removedStart moving lib.php callbacks to hooks
4.422 Apr 2024Dependency injection containerOptional for now
4.57 Oct 2024Routing, AI subsystemOptional for now
5.014 Apr 2025Bootstrap 5, Atto and subplugins.php removedRebuild Atto plugins, check templates
5.16 Oct 2025/public web root, routing engineMove plugin folders, fix paths
5.220 Apr 2026React, Composer, deprecations up to 4.4 removedReplace removed functions
5.35 Oct 2026Symfony Console, DI by attributes, Classic theme removedRebuild 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:

  1. YUI from 2.0, as part of the new output system.
  2. AMD modules from 2.9, with jQuery alongside.
  3. ES module syntax from 3.8, compiled back to AMD.
  4. React, TypeScript and real ES modules from 5.2, with a {{#react}} Mustache helper and import maps.1415
  5. 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-many lib.php callbacks. When a plugin has both, the lib.php callback 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 route namespace with a \core\router\route attribute.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 /public web root, 5.1. Web-accessible files moved into public/ (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.php callbacks that now have hooks.
  • Written for 4.x: mostly the /public move, 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

  1. Moodle HQ, Releases. ↑

  2. MoodleDocs, Moodle 1.7 release notes. ↑

  3. MoodleDocs, Moodle 2.0 release notes. ↑

  4. MoodleDocs, Moodle 2.4 release notes. ↑

  5. MoodleDocs, Moodle 2.6 release notes. ↑

  6. MoodleDocs, Moodle 2.7 release notes. ↑

  7. MoodleDocs, Moodle 2.9 release notes. ↑

  8. MoodleDocs, Moodle 3.2 release notes. ↑

  9. Moodle HQ, JavaScript Modules. ↑

  10. MoodleDocs, Moodle 3.8 release notes. ↑

  11. Moodle HQ, Moodle 4.3 release notes. ↑

  12. Moodle HQ, Moodle 5.0 release notes. ↑ ↑ ↑

  13. MoodleDocs, Moodle 3.5 release notes. ↑

  14. Moodle HQ, Moodle 5.2 release notes. ↑ ↑

  15. Moodle HQ, Frontend development guide (5.2). ↑ ↑

  16. Moodle HQ, Moodle 5.3 release notes. ↑ ↑ ↑

  17. Moodle HQ, Moodle 4.0 release notes. ↑

  18. Moodle HQ, Hooks API. ↑

  19. Moodle HQ, Dependency Injection. ↑

  20. Moodle HQ, Routing. ↑

  21. Moodle HQ, Moodle 5.1 release notes. ↑ ↑

  22. 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