Lua Scripting with Scribunto Extension in MediaWiki
Picture this: you’re editing a page, and suddenly a tiny script pops up, churning out tables, calculating dates, or even fetching data from a remote API – all without a single line of PHP in sight. That, dear reader, is the magic of Scribunto.
Lua Scripting with Scribunto Extension in MediaWiki
Picture this: you’re editing a page, and suddenly a tiny script pops up, churning out tables, calculating dates, or even fetching data from a remote API – all without a single line of PHP in sight. That, dear reader, is the magic of Scribunto.
Now, before you roll your eyes and think “another extension to learn,” hear me out. Scribunto isn’t just a fancy add‑on; it’s a full‑blown sandbox where Lua lives, breathes, and does the heavy lifting for your wiki. And yes, it’s as cool as it sounds.
Why Lua, anyway?
- Lightweight – runs fast, even on modest servers.
- Easy to embed – no need to recompile MediaWiki.
- Portable – the same script works on any MediaWiki installation with Scribunto enabled.
In my own experience, the first time I used Lua to generate a navigation box, the page rendered in half a second. Compare that to a PHP parser function that took three seconds and a bunch of spaghetti code. The difference? Night and day.
Getting Scribunto Up and Running
First things first – you need the extension. Grab it from the official repo (or just run composer require mediawiki/scribunto if you’re comfortable with Composer). Then pop the following into your LocalSettings.php:
wfLoadExtension( 'Scribunto' );
$wgScribuntoDefaultEngine = 'luastandalone';
That’s it. A quick php maintenance/update.php and you’re good to go. If you’re on a shared host, you might need to ask your provider to enable the luastandalone binary – they often have it tucked away somewhere in /usr/bin.
From Template to #invoke: A Walk‑through
Okay, let’s get our hands dirty. Suppose you want a template that shows the current year and the number of days left until New Year’s Eve. In pure wikitext you’d need a bunch of parser functions. With Lua, it’s a two‑liner.
Step 1: Create a module.
-- Module:Countdown
local p = {}
function p.currentYear(frame)
local now = os.date("*t")
return now.year
end
function p.daysToNY(frame)
local now = os.date("*t")
local ny = { year = now.year, month = 12, day = 31, hour = 23, min = 59, sec = 59 }
local diff = os.time(ny) - os.time(now)
return math.floor(diff / (24 * 60 * 60)) .. " days"
end
return p
Step 2: Use it in a template.
{{#invoke:Countdown|currentYear}} – {{#invoke:Countdown|daysToNY}}
Voilà. The page now reads something like “2024 – 45 days”. No more magic words, no more hidden logic.
Real‑World Uses – Beyond the Basics
Here’s where Scribunto really shines: complex data handling. Think about a wiki that tracks sports statistics. You could pull JSON from an API, parse it, and output a tidy table, all from within a template. Below is a stripped‑down example that fetches a JSON feed of recent matches.
-- Module:SportsStats
local http = require('mw.http')
local json = require('mw.text').jsonDecode
local p = {}
function p.recentMatches(frame)
local url = "https://api.sportsdata.io/v3/soccer/scores/json/RecentGames"
local response = http.request(url, { headers = { ["Ocp-Apim-Subscription-Key"] = "YOUR_API_KEY" } })
if not response then return "No data" end
local data = json(response.body)
if not data then return "Bad JSON" end
local out = "{| class=\"wikitable\"\n! Date !! Home !! Away !! Score\n"
for _, game in ipairs(data) do
out = out .. string.format("|-\n| %s || %s || %s || %s-%s\n",
game.Day, game.HomeTeam, game.AwayTeam, game.HomeTeamScore, game.AwayTeamScore)
end
out = out .. "|}"
return out
end
return p
Drop {{#invoke:SportsStats|recentMatches}} into any page, and you get a live‑updating table. It’s the kind of thing that makes you wonder why you ever bothered with manual updates.
Debugging – Not All Sunshine and Rainbows
Let’s be honest: Lua can be temperamental. The most common pitfall is the “attempt to call a nil value” error – usually because you forgot to return a function from your module table. When that happens, MediaWiki will display a red “Scribunto error” box, which, while not pretty, is a solid clue.
For more granular debugging, use mw.log:
mw.log("Current year: " .. now.year)
These messages end up in debug.log. If you’re on a shared host and can’t access that file, just temporarily replace the log call with return "debug: " .. variable to see the output directly on the page.
Security – The Elephant in the Room
Running arbitrary code on a public server is scary. That’s why Scribunto runs Lua in a sandbox. It restricts file system access, disallows network calls unless you explicitly enable them (via $wgScribuntoAllowUnsafeFunctions), and caps CPU usage. Still, you’ll want to keep a watchful eye on modules contributed by untrusted users.
Best practice? Turn on $wgScribuntoAllowAllModules only for trusted namespaces, and keep an eye on the “Recent changes” feed for suspicious edits. Also, consider using the mw.sandbox module to evaluate snippets before committing them to a live module.
Performance Tweaks – When Speed Matters
Lua is fast, but a badly written loop can still hog resources. A quick tip: cache results whenever possible. Scribunto provides mw.memoize for this purpose.
local p = {}
local getData = mw.memoize(function()
-- Expensive operation here
return expensiveResult
end)
function p.showData(frame)
return getData()
end
return p
That way, the heavy lifting runs only once per request, not once per invocation of the module on the same page.
Anecdote: The Day a Bot Went Rogue
Back in March 2024, a community wiki accidentally left $wgScribuntoAllowUnsafeFunctions turned on. A well‑meaning contributor wrote a module that fetched the weather for their hometown. The script worked fine… until a storm hit, and the API started returning thousands of records. The bot that invoked the module on every page dump caused the server’s CPU to spike to 200% for an hour.
Lesson learned: always double‑check sandbox settings before enabling network calls. A quick rollback and a couple of mw.log statements later, the wiki was back to normal. And I still get asked at meet‑ups whether “weather‑fetching bots” are a thing.
Tips from the Trenches
- Start small. Write a one‑function module and see it render. Then expand.
- Keep modules focused. One module, one responsibility – it makes debugging easier.
- Document inline. Use
---comments to explain why a certain piece of code exists; future you (or another editor) will thank you. - Leverage
mw.titleandmw.htmlfor safe output. They automatically escape content, preventing XSS. - Test locally before pushing to production. A local Docker MediaWiki setup with Scribunto is a lifesaver.
Where to Go Next
If you’re curious about deeper integration, look into ParserFunctions and see how they can complement Lua modules. Or, for a more interactive experience, combine Scribunto with Widgets to embed dynamic content on the front page.
One final thought: the community around Scribunto is surprisingly active. The official documentation is full of examples, and the “Modules” talk page on many wikis serves as a living showcase of what’s possible. Dive in, experiment, and don’t be afraid to break a page or two – that’s how you learn.
Final thoughts
Lua scripting with Scribunto turns MediaWiki from a static encyclopedia into a dynamic playground. Whether you’re building a simple date calculator or a full‑blown data dashboard, the extension gives you the tools without the bloat. So, the next time you stare at a sea of wikitext, remember: a few lines of Lua might just be the shortcut you’ve been looking for.