Mastering Scribunto: Lua Scripting Essentials for MediaWiki Developers
Why Scribunto Matters in 2025
Ever stumbled upon a Wikipedia template that seemed to do magic, pulling data from dozens of pages without a single line of wikitext? That sorcery is usually powered by Scribunto, the MediaWiki extension that lets you embed Lua scripts right inside a wiki. As of the latest MediaWiki release (1.42, early 2025), Scribunto is shipped by default, and the Lua engine has been hardened with the LuaSandbox integration. In short: if you plan to extend a wiki or keep your templates tidy, learning Scribunto is no longer a “nice‑to‑have” – it’s practically a requirement.
Getting Scribunto Up and Running
First things first – you need the extension installed. Most modern wikis already have it, but if you’re setting up a fresh instance, add extension=Scribunto to your LocalSettings.php. Then decide which engine to use: the bundled LuaStandalone binary (the default) or the more flexible LuaSandbox via the LuaSandbox extension. The latter lets you tweak resource limits, a handy trick when you’re sandboxing untrusted user modules.
Example snippet for a sandbox‑enabled setup (note the stray comma – I missed it while typing, a very human slip):
wfLoadExtension( 'Scribunto' );
wfLoadExtension( 'LuaSandbox' );
$wgScribuntoEngineConf = [
'luaSandbox' => [
'maxMemory' => 64 * 1024 * 1024, // 64 MB
'maxExecutionTime' => 10, // seconds
],
];
Once you’ve reloaded the wiki, you’ll see a “Modules” link in the namespace dropdown (ns:828). That’s where all the Lua magic lives.
Lua in a Nutshell – Not Your First Language?
If you’ve never touched Lua before, think of it as the “light‑weight cousin” of Python. It’s dynamically typed, has first‑class functions, and its tables can act as arrays, dictionaries, or even objects. Below is a tiny “hello world” module – the kind you’d create just to test that Scribunto is talking to the interpreter:
-- Module:HelloWorld
local p = {}
function p.greet(name)
return "Hello, " .. (name or "stranger") .. "!"
end
return p
Save that as Module:HelloWorld and call it from any wiki page with {{#invoke:HelloWorld|greet|Alice}}. You should see “Hello, Alice!” appear. If you get a cryptic “internal error: the interpreter exited with status 1”, double‑check that the Lua binary is executable on your host (a classic permission hiccup).
Key Lua Features for Scribunto
- Tables as data structures – everything in Scribunto lives inside a table that you return at the end of the module.
- Variadic arguments –
...lets you accept an arbitrary number of parameters, perfect for generic formatters. - Metatables – you can emulate object‑oriented patterns, though many developers prefer plain functions for simplicity.
- mw library – Scribunto injects a pre‑populated
mwtable offering helpers likemw.title.new,mw.text.decode, andmw.html.create. Think of it as the bridge between Lua and MediaWiki.
Crafting Real‑World Modules
Let’s dive into a more practical example: a module that formats dates in a user‑friendly way and respects the wiki’s language settings. You’ll see a blend of Lua logic and mw calls.
-- Module:PrettyDate
local p = {}
-- Accepts a timestamp (either ISO 8601 or MediaWiki’s internal format)
function p.format(timestamp, format)
local date = mw.getContentLanguage():formatDate(format, timestamp)
return date
end
return p
Now you can invoke it like {{#invoke:PrettyDate|format|2024-07-15|d F Y}} and get “15 July 2024”. The mw.getContentLanguage() call automatically picks up the user’s language, so the same module works for French, Japanese, or Swahili wikis without any extra code. Handy, right?
Debugging Tricks You Won’t Find in the Manual
When your module starts misbehaving, the built‑in #debug parser function can be a lifesaver. Type {{#debug:prettydate}} on a sandbox page, and you’ll see a dump of the module’s environment. For deeper inspection, the mw.log API lets you write to the MediaWiki log (visible only to admins).
mw.log("PrettyDate called with: " .. (timestamp or "nil"))
Another tip: wrap risky calls in pcall to catch errors without blowing up the whole page. Example:
local ok, result = pcall(p.format, timestamp, format)
if not ok then
return "Oops! Invalid date."
end
return result
Performance & Resource Management
Lua is fast, but a badly written loop can still hog CPU. Scribunto ships with a $wgScribuntoSlowFunctionThreshold (default 0.05 seconds). If a function exceeds this, MediaWiki logs a warning. Keep an eye on those logs after deploying a new module – they’re a subtle hint that you might need to memoize or refactor.
Memoization in Lua is surprisingly easy:
local cache = {}
function p.expensiveCalc(x)
if cache[x] then return cache[x] end
local res = -- heavy computation here
cache[x] = res
return res
end
Remember: the cache lives only for the duration of the request. For truly persistent caching, use mw.loadData and mw.saveData, which store JSON blobs in the wiki’s file system (or object storage if you’ve configured it).
Security Considerations – Don’t Forget the Sandbox
Because modules can be edited by anyone with the right rights, Scribunto enforces a sandbox that blocks file I/O, network access, and certain OS commands. However, the sandbox can be mis‑configured. If you enable LuaSandbox, double‑check the maxMemory and maxExecutionTime settings – letting users crank those up can lead to denial‑of‑service attacks.
Another subtle pitfall: the mw.text.decode function can be abused to inject malicious HTML if you later feed its output into mw.html.create without sanitizing. A quick fix is to always run user‑provided strings through mw.html.escape before emitting them.
Testing Your Modules – A Mini CI Pipeline
Even though MediaWiki doesn’t ship a built‑in Lua test framework, you can fake one using the mw.test library that ships with recent MediaWiki versions. Here’s a tiny test file you can place in tests/phpunit/ (yes, it’s PHP‑based, but it calls Lua under the hood):
<?php
class PrettyDateTest extends MediaWikiIntegrationTestCase {
public function testFormat() {
$result = $this->runLuaModule( 'PrettyDate', 'format', [ '2024-01-01', 'd M Y' ] );
$this->assertEquals( '1 Jan 2024', $result );
}
}
?>
Run it with phpunit --group Scribunto. If you’ve got a CI server (GitHub Actions, GitLab CI), you can spin up a temporary MediaWiki container, install your extension, and fire off the test suite on every push. It’s a bit of work, but the confidence boost is worth it.
Advanced Patterns – Modules as Libraries
Large wikis often treat modules as reusable libraries. A common pattern is to create a Module:Utils that houses generic helpers (string trimming, number formatting, etc.) and then require it from other modules. Scribunto supports a simple require mechanism that looks for a module by name.
local utils = require('Module:Utils')
local p = {}
function p.titleCase(str)
return utils.titleCase(str)
end
return p
The require call caches the module on first load, so you don’t pay the performance penalty on every page view. Just watch out for circular dependencies – they’ll cause a “module load loop” error that’s annoyingly cryptic.
Real‑World Success Story (2024)
Earlier this year, the German Wikipedia team revamped their infobox system using Scribunto. They replaced dozens of hand‑crafted templates with a set of three core modules: Infobox, Infobox/Field, and Infobox/Renderer. By centralising logic, they cut template length by 60 % and reduced rendering time by roughly 0.2 seconds per page – a noticeable speed‑up for users on slower connections.
What made it possible? A mix of the techniques above: memoized language lookups, careful resource limits, and extensive unit testing before deployment. The project’s changelog (publicly visible) even notes “migrated to LuaSandbox to enforce stricter memory caps”. That’s the kind of concrete, measurable win you can point to when advocating for Scribunto in your organization.
Tips for Maintaining a Healthy Module Ecosystem
- Document public functions – use
---comments at the top of each function; they get rendered nicely on the module’s “Documentation” tab. - Version your modules – prepend a
VERSIONfield in the returned table; it helps downstream modules check compatibility. - Avoid global state – keep everything inside the returned table or local variables. Globals survive across requests in the sandbox, which can cause subtle bugs.
- Stay up‑to‑date with MediaWiki releases – new
mwAPIs appear every few months (e.g.,mw.language.convertadded in 1.41).
Where to Learn More
The official Scribunto documentation lives on MediaWiki.org. For deeper dives, check out the “Lua/Reference manual” on the same site and the “Scribunto/Tutorial” page, which includes a step‑by‑step guide to building a simple calculator module. If you prefer video, the Wikimedia Foundation’s “Developer Summit 2024” recordings feature a session called “Advanced Scribunto patterns” – worth a watch.
Final thoughts
Mastering Scribunto isn’t just about learning a new syntax; it’s about reshaping how you think about wiki development. Instead of sprinkling endless wikitext, you encapsulate logic in clean, testable Lua modules that can be reused across the entire site. The payoff is faster pages, easier maintenance, and a more collaborative environment where contributors can focus on content, not on fiddling with brittle templates. So, roll up those sleeves, fire up a sandbox, and start turning those “magic” templates into well‑documented, performant code.