Exploring the Scribunto Extension for Lua Scripting in MediaWiki
Why Scribunto Matters More Than You Think
Picture this: you’re editing a massive wiki template, and every change feels like rearranging deck chairs on the Titanic. Suddenly, a friend whispers, “Try Scribunto.” You blink. “What’s that?” A few minutes later you’re staring at a tiny Module file, and the whole thing clicks. No more spaghetti‑laden templates, just clean Lua functions doing the heavy lifting.
That moment—when a piece of code suddenly feels like a conversation rather than a chore—is what this post is about. I’m not going to hand‑hold you through the entire extension doc (you can skim that later); instead, let’s wander through the quirks, the wins, and the occasional head‑scratchers that come with scripting in MediaWiki using Scribunto.
Getting Your Feet Wet
First off, Scribunto isn’t a brand‑new add‑on; it’s been around since 2012, quietly powering infoboxes on Wikipedia and countless other wikis. If you’ve ever seen an infobox that updates itself, thank Lua. The extension lives under Extension:Scribunto and ships with a sandboxed Lua interpreter called LuaSandbox. In plain English: you write Lua, MediaWiki runs it safely, and you get the output back in wikitext.
Here’s the minimal setup you need on a typical MediaWiki installation (assuming you have root access):
# In your LocalSettings.php
wfLoadExtension( 'Scribunto' );
$wgScribuntoDefaultEngine = 'luastandalone';
$wgScribuntoEngineConf['luastandalone']['luaPath'] = '/usr/bin/lua';
Don’t forget to run php maintenance/update.php afterward. If you skip that, you’ll get a bewildered “Extension not found” error that feels like the system is silently judging you.
Modules vs. Templates: A Quick Analogy
Think of a Template as a sticky note and a Module as a small toolbox. A sticky note can hold a few lines of text, maybe a tiny table. The toolbox? It houses functions, loops, conditionals—everything you need to compute values on the fly. When you call a module from a template, you’re basically borrowing a wrench from the toolbox.
Example: instead of hard‑coding the current year in dozens of places, you write a one‑liner module that returns os.date('%Y'). Then, every template just calls {{#invoke:Date|year}}. If the year changes, you don’t touch a single template. It’s that simple, yet the mental shift can feel like moving from a horse‑drawn carriage to a scooter.
Writing Your First Module
Alright, roll up your sleeves. Below is a tiny “HelloWorld” module that demonstrates the basic structure. Feel free to copy‑paste it into a page titled Module:HelloWorld on your wiki.
-- Module:HelloWorld
local p = {}
function p.greet(frame)
local name = frame.args[1] or "stranger"
return "Hello, " .. name .. "! 👋"
end
return p
Notice the frame argument? It’s the way Scribunto hands you whatever parameters the caller supplies. You can then invoke it like this:
{{#invoke:HelloWorld|greet|Alice}}
The output? “Hello, Alice! 👋”. If you forget to pass a name, the module falls back to “stranger”. That fallback is a tiny safety net—something I wish more extensions would provide by default.
Common Pitfalls (and How to Dodge Them)
- Infinite loops: Lua runs inside a sandbox with a hard‑coded instruction limit. If you accidentally write
while true do end, the interpreter will abort after a few milliseconds and you’ll see a “Scribunto error: execution limit reached”. A quick fix is to add a counter and break after, say, 10,000 iterations. - Global namespace pollution: By default, every module gets its own environment, but if you use
_Gyou can unintentionally overwrite a function that another module relies on. Treat_Glike the last slice of pizza—only take it if you really need it. - Missing libraries: The sandbox only ships with a handful of Lua libraries (string, table, math, etc.). If you need something fancy like JSON parsing, you’ll have to enable the
mw.ext.JSONlibrary or write a tiny parser yourself.
Real‑World Use Cases You Might Not Expect
Beyond infoboxes, Scribunto powers:
- Dynamic navigation boxes that change based on page categories.
- Statistical tables that pull data from other pages, crunch numbers, and display results without a single manual edit.
- Localization helpers that fetch language‑specific strings from
Messageobjects, making multilingual wikis a breeze.
One quirky example from the gaming community: a module that simulates dice rolls for tabletop RPG pages. Users type {{#invoke:Dice|roll|2d6+3}} and the module returns a random result, complete with a tiny graphic of the dice. It’s a neat illustration of how Lua can blend logic with a dash of fun.
Performance Considerations (Yes, They Matter)
If you’ve ever watched a page load slower than a snail on a hot sidewalk, you know performance can be a deal‑breaker. Scribunto caches the output of module calls, but the cache key is a hash of the module’s source code plus the arguments. Change the module, and the cache busts—meaning the next request re‑executes the Lua code.
Here’s a rule of thumb I live by: keep heavy computation inside a module, but avoid loops that scale with the number of pages. For example, a module that iterates over every page in a large category will choke the server. Instead, pre‑compute aggregates during a nightly job and store them in a simple PageProps property; then have the module read that property.
Testing Your Modules – A Mini‑Guide
Testing Lua code in MediaWiki isn’t as straightforward as running lua mymodule.lua locally, because you need the mw library. Fortunately, the extension ships with a test harness you can invoke from the command line:
php maintenance/runTests.php --group=Scribunto
Alternatively, create a sandbox page on your wiki, like Module:TestSandbox, and call your functions from there. It feels a bit like using a kitchen sink for debugging, but it works.
Tips for Writing Readable Lua in Scribunto
Even though Lua is terse, readability can still suffer. Here are some habits I’ve picked up:
- Use explicit
localfor every variable—global leaks are a silent source of bugs. - Prefer
string.formatover concatenation for complex strings; it reads like a template. - Document functions with a short comment block. Scribunto doesn’t parse docstrings, but future you (or a collaborator) will thank you.
Example of a well‑commented function:
--- Returns a formatted date string.
-- @param timestamp ISO‑8601 timestamp, e.g. "2025-10-03T14:00:00Z"
-- @return Human‑readable date, e.g. "Oct 3, 2025"
function p.formatDate(timestamp)
local year, month, day = timestamp:match("(%d%d%d%d)%-(%d%d)%-(%d%d)")
return os.date("%b %d, %Y", os.time{year=year, month=month, day=day})
end
Where to Find More Help
If you’re stuck, the community is surprisingly responsive. The official extension page hosts a reference manual, and the Lua tutorial walks through common patterns. For edge‑case questions, pop into the Support Desk or the #mediawiki channel on Libera.Chat.
One thing I’ve learned over the years: don’t be shy about posting “I tried X and got Y”. The community loves a good puzzle, and you’ll often get a one‑liner solution that saves you hours.
Final Thoughts (But Not a Formal Conclusion)
So, does Scribunto feel like a magic wand or a Swiss army knife? Depends on how you wield it. For simple string manipulation, a module can be overkill—just use a template. For anything that needs loops, conditionals, or external data sources, Scribunto shines like a lighthouse in a sea of static wikitext.
My parting advice: start small. Write a “HelloWorld” module, sprinkle a few #invoke calls across your wiki, and watch the pages become a bit more dynamic. Then, as confidence builds, refactor a messy template into a tidy module. You’ll notice the wiki feels more maintainable, and you’ll get that satisfying “aha!” moment a few times a week—just the way good code should be.