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 _G you can unintentionally overwrite a function that another module relies on. Treat _G like 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.JSON library or write a tiny parser yourself.

Real‑World Use Cases You Might Not Expect

Beyond infoboxes, Scribunto powers:

  1. Dynamic navigation boxes that change based on page categories.
  2. Statistical tables that pull data from other pages, crunch numbers, and display results without a single manual edit.
  3. Localization helpers that fetch language‑specific strings from Message objects, 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 local for every variable—global leaks are a silent source of bugs.
  • Prefer string.format over 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.

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