Making a change to 300 Moodle themes used to mean making it 300 times. Once the theme settings lived outside the CMS, in a database I controlled, it meant running a script.
The problem
When I joined Learning Pool, there were roughly 80 Moodle instances — one per customer. Learning Pool specialised in custom Moodle LMS deployments for UK local government, primarily to handle annual compliance training, so most of those instances belonged to individual councils. Each was a separate codebase, manually maintained: core updates, custom features, and theme changes all applied by hand, one instance at a time.
At that scale, it was already unsustainable. If a customer raised a theme bug, you had to log into their instance, work out what had drifted from what you'd intended, and fix it there. If a new customer came on with a new brand, you reproduced the same setup from scratch — clicking through Moodle's admin interface, manually. If you needed to roll a change across every instance, you were doing it 80 times, and hoping you were doing it consistently.
The root of the problem was where Moodle stored theme settings: in a database table inside each instance. That meant the canonical version of a customer's theme lived inside the system they were using — not somewhere you controlled, not somewhere you could version, not somewhere you could script against.
How it worked
A parallel effort from the development team was already underway to consolidate instances: instead of one codebase per customer, multiple sites would run off the same one. That gave me the opening I needed. If we were rethinking how the instances worked, we could rethink how the themes worked too.
I wrote scripts with SQL queries to extract theme settings from each instance and pull them into a centralised, dedicated database — one place that held the theme data for all our customers, separated from the systems that consumed them. The theme values were no longer properties of individual Moodle instances; they were independent data that could be read and applied however was needed.
Alongside that, I built scripts to spin up a local Moodle environment: the latest codebase, our customisations, the previous day's data backup, and the theme values pulled from that centralised database. This made it possible to develop and debug without touching live instances. Instead of making changes on a customer's site and hoping nothing broke, you had a local environment that matched production closely enough to catch problems before they shipped.
Advanced themes were handled by injecting custom CSS and images into Moodle's theme layer on top of the base settings. Those customisations went into the same centralised database — which expanded beyond the values Moodle's own theme table expected, to hold whatever a customer's brand actually required.
A precursor to tokens
The terminology hadn't arrived yet. But what I was building followed the same principle the industry later codified as design tokens: separate the design values from the systems that consume them, maintain them in one place, push them to wherever they're needed.
I was doing this just as those ideas were beginning to be formalised elsewhere. I wasn't aware of that at the time; I was solving a practical problem, not following a field. But looking back from a career that's been largely about design systems infrastructure, it tracks. The instinct that made this feel like the right solution in 2010 is the same one that shows up later in how I structured the work at AVEVA and Cycloid.
Where it landed
By the time I left Learning Pool in 2013, the number of supported instances had grown from 80 to over 300 — all maintained through that centralised database and the scripts built around it. A theme change that used to take hours now took a few keystrokes. Bug turnaround shrank significantly because local environment setup was fast and reliable. New customers got their themes from the same pipeline as everyone else.
It wasn't the most glamorous work. At the time I wasn't thinking of it in terms of design systems — I was thinking: this is unsustainable, and I need to fix it. Those descriptions aren't really in conflict.