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_open function 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/*/lua

Tell MediaWiki to load it. In LocalSettings.php add:

wfLoadExtension( 'Scribunto' );
$wgScribuntoDefaultEngine = 'luastandalone'; // the bundled binary works for most setups

Grab the extension (if you’re on MediaWiki 1.34+ it’s already bundled).

cd extensions && git clone https://gerrit.wikimedia.org/r/mediawiki/extensions/Scribunto

That’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:

  1. Chunk your work. Instead of a single massive loop, break the work into several calls using #invoke with 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:Infobox family pulls data from Wikidata, formats dates, and adds language‑specific links, all in Lua.
  • Navigation trees – the Module:Navbox code 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.http library 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

Subscribe to MediaWiki Tips and Tricks

Don’t miss out on the latest articles. Sign up now to get access to the library of members-only articles.
jamie@example.com
Subscribe