Harnessing Lua Scripting in MediaWiki with the Scribunto Extension
Why Lua feels like a secret weapon for wikis
Ever stared at a wikitext page and thought, “there’s gotta be a smarter way”? You’re not alone. In the wild world of MediaWiki, the Scribunto extension quietly hands you a scripting toolbox that looks suspiciously like a Swiss army knife – compact, versatile, and ready for the unexpected.
It’s not a hype‑driven fad; it’s the same Lua that powers games, embedded devices, and the Roblox engine. The moment you embed a {{#invoke:}} call, you’re stepping out of the static‑template realm and into a world where loops, conditionals, and even tiny APIs become your allies.
Don’t get me wrong: templates still have their place. But when you need to crunch numbers, fetch data from an external service, or generate a navigation tree on the fly, Lua is the quiet accountant in the corner, doing the heavy lifting while the rest of the wiki watches in awe (or, you know, mild confusion).
Getting Scribunto up and running
- Make sure the PHP
proc_openfunction isn’t locked down – a common roadblock on shared hosts. You’ll see a “Script error” if it’s disabled.
Give the Lua binaries a little love:
chmod a+x extensions/Scribunto/includes/Engines/LuaStandalone/binaries/*/luaTell MediaWiki to load it. In LocalSettings.php add:
wfLoadExtension( 'Scribunto' );
$wgScribuntoDefaultEngine = 'luastandalone'; // the bundled binary works for most setupsGrab the extension (if you’re on MediaWiki 1.34+ it’s already bundled).
cd extensions && git clone https://gerrit.wikimedia.org/r/mediawiki/extensions/ScribuntoThat’s it. In theory. In practice you might run into an SELinux “noexec” flag or a missing mbstring extension – both easy fixes, but they tend to pop up exactly when you’re about to give up.
Writing your first module – the “Hello World” of Scribunto
Modules live in the Module namespace. Think of them as tiny Lua libraries that the wiki can call at will. Create a page called Module:Demo and drop in this minimal code:
local p = {}
function p.greet( name )
name = name or "stranger"
return "Greetings, " .. name .. "!"
end
return p
Now sprinkle it onto any article:
{{#invoke:Demo|greet|Bob}}It spits out “Greetings, Bob!”. Simple, right? The magic is that this code runs on the server, not in the client’s browser, and you can reuse p.greet from any other module – no copy‑paste required
Advanced tricks – when you need more than a greeting
Let’s say you want a dynamic infobox that pulls data from Wikidata. You can tap the mw.wikibase library (provided by the Wikibase client extension) like this:
local p = {}
local wb = require 'mw.wikibase'
function p.infobox( qid )
local entity = wb.getEntity( qid )
if not entity then return "Entity not found." end
local label = entity:getLabel()
local description = entity:getDescription()
return string.format( "'''%s''' – %s", label, description )
end
return p
Now on a page you write {{#invoke:Demo|infobox|Q42}} and you get a one‑liner about Douglas Adams. Not bad for a few lines of Lua, huh?
And if you need a bit of math, the standard math library is there, plus MediaWiki‑specific helpers like mw.title (to manipulate page titles) and mw.html (to safely build HTML fragments).
Performance tuning – because speed matters
Lua in Scribunto runs inside a sandboxed executable. That sandbox imposes a CPU and memory limit (default ~7 seconds, ~50 MiB). Most modules finish well before that, but if you’re looping over hundreds of items you might hit the ceiling. Two quick fixes:
- Chunk your work. Instead of a single massive loop, break the work into several calls using
#invokewith pagination arguments. The wiki caches each call, so subsequent requests are cheap.
Switch to the luasandbox engine (a PHP extension written in C). It’s faster because it avoids spawning a separate process. In LocalSettings.php set:
$wgScribuntoDefaultEngine = 'luasandbox';Remember: the sandbox is there to protect the server. If you disable limits, you open a door to denial‑of‑service attacks – not worth it.
Debugging pitfalls
When a module blows up, MediaWiki shows a red “Script error” link. Click it and you’ll see a stack trace, but the trace is often cryptic because the Lua interpreter runs in a separate process. The trick is to enable error logging:
$wgScribuntoEngineConf['luastandalone']['errorFile'] = '/tmp/scribunto_error.log';
Now every panic ends up in /tmp/scribunto_error.log. Open it, and you’ll see messages like “attempt to index field ‘text’ (a nil value)”. That usually means you tried to call mw.title:newFromText() with an empty string – a subtle bug that’s easy to miss during development.
Another common gremlin: the proc_open function disabled in php.ini. The error looks like “proc_open(): open_basedir restriction in effect”. Fix it by updating disable_functions or, on shared hosting, ask your provider for a custom PHP handler.
Real‑world examples
- Infoboxes that auto‑populate – Wikipedia’s
Module:Infoboxfamily pulls data from Wikidata, formats dates, and adds language‑specific links, all in Lua. - Navigation trees – the
Module:Navboxcode walks a hierarchy of pages, builds nested lists, and respects user language preferences. - Dynamic citations – some wikis generate citation templates on the fly based on DOI lookups, using the
mw.httplibrary to fetch JSON from external APIs.
These aren’t just showcase projects; they’re the backbone of many high‑traffic wikis (yes, even the ones you browse daily). The pattern is the same: a small Lua module, a #invoke call, and a lot of saved keystrokes.
Gotchas that made me pull my hair out
First, luastandalone ships binaries for Linux, macOS, and Windows, but they’re compiled against a specific glibc version. On an older distro you’ll see “version ‘GLIBC_2.11’ not found”. The fix? Either upgrade the OS or drop in a custom Lua binary and point the engine at it:
$wgScribuntoEngineConf['luastandalone']['luaPath'] = '/usr/local/bin/lua5.1';
Second, the sandbox blocks os.execute – good for security, bad for those rare cases where you truly need to spawn a helper script. The workaround is to write a Scribunto external library in PHP that does the system call, then expose it to Lua.
Finally, remember that every #invoke call creates a fresh environment. Global variables don’t persist across calls. If you need caching, use mw.loadData to store a table in a JSON file or, for bigger data, the memcached extension.
Where to go from here
If you’re still on the fence, try building a tiny “random quote” module that pulls a line from a JSON file on the wiki. It’ll teach you file I/O, JSON parsing, and the basics of mw.text. Then graduate to a more ambitious project: a table of recent changes filtered by namespace and user, rendered as an HTML table with sortable columns – all powered by Lua.
And don’t forget the community. The Scribunto Phabricator tag is a goldmine of bug reports, feature ideas, and patches. If you stumble upon a clever trick, share it. You might just see your module end up on the “Modules used by many wikis” list – a quiet badge of honor for any wiki‑hacker.
Final thoughts
Lua isn’t a magic bullet, but it’s a surprisingly powerful tool that fits snugly into MediaWiki’s architecture. With Scribunto, you get a sandboxed environment, a decent standard library, and a pathway to tap into the broader MediaWiki ecosystem (Wikibase, Semantic MediaWiki, you name it). The learning curve isn’t steep – a few dozen lines of code can replace dozens of template edits – yet the payoff is huge: faster pages, cleaner wikitext, and a more maintainable wiki