Using Scribunto Extension for Lua Scripting in MediaWiki
Why Scribunto matters (and why you might be curious right now)
Why Scribunto matters (and why you might be curious right now)
Okay, picture this: you’re editing a wiki page and you suddenly wish you could run a tiny script right there, without having to spin up a whole separate server or embed a massive JavaScript blob. Enter Scribunto, the MediaWiki extension that lets you write Lua modules and call them from wikitext with {{#invoke:…}}. It feels a bit like adding a Swiss‑army knife to your toolbox – you get a programmable layer that’s still safe, sandboxed, and, best of all, reusable across pages.
Since MediaWiki 1.34 the extension ships with the core, but you’ll still have to tweak a few settings to make it sing. Below you’ll find a loosely‑structured walk‑through that jumps from “hey, this is how you install it” to “here’s a quirky gotcha that tripped me up last week”. No‑fluff, a few sidebars, and yes – a dash of personality.
Getting Scribunto onto your wiki (the “install‑it‑now” part)
- Step 3 – verify. Visit Special:Version and look for “Scribunto” in the extensions list. If it shows up, you’re good to go.
Step 2 – tell MediaWiki to load the extension. Drop these lines at the very bottom of LocalSettings.php (yes, the bottom, because some other extensions may want to override defaults earlier):
wfLoadExtension( 'Scribunto' );
$wgScribuntoDefaultEngine = 'luastandalone'; // the bundled binary works for most setupsStep 1 – grab the code. If you’re on a recent MediaWiki release you probably already have the Scribunto folder under extensions/. Otherwise, clone it straight from Wikimedia’s repo:
cd extensions/
git clone https://gerrit.wikimedia.org/r/mediawiki/extensions/ScribuntoIf you prefer a tarball, the same URL works with a .tar.gz suffix.
Do you need the Lua binary?
Most installations are happy with the bundled binaries (they cover Linux x86‑64, macOS, and Windows). But if you’re on an exotic platform – say a Raspberry Pi or an old 32‑bit server – you’ll have to point Scribunto at a custom interpreter. Add this to LocalSettings.php (replace /usr/bin/lua5.1 with wherever your binary lives):
$wgScribuntoEngineConf['luastandalone']['luaPath'] = '/usr/bin/lua5.1';Basic concepts you should know before you start scripting
- Modules live in their own namespace. By default it’s
Module:(ID 828). When you create a new Lua file, you do it asModule:MyCoolThing. - Sandboxed environment. Each call runs in its own sandbox – variables don’t leak between calls, which keeps things tidy but also means you can’t share state without explicit storage (e.g.
mw.store).
The #invoke parser function. This is how you call a function inside a module from normal wikitext. Syntax is:
{{#invoke:Module_name|function_name|arg1|arg2|…}}Quick “Hello, world!” example
Let’s get our hands dirty. Create a page Module:Hello with this content:
local p = {}
function p.greet(frame)
local name = frame.args[1] or "world"
return "Hello, " .. name .. "!"
end
return pNow drop this onto any wiki page:
{{#invoke:Hello|greet|Alice}}and you’ll see “Hello, Alice!”. If you leave out the argument you get “Hello, world!”. Easy, right?
Going a step further – real‑world patterns
Most wikis use Scribunto for slightly more sophisticated tasks: infoboxes, navigation templates, or even on‑the‑fly calculations. Below are three common patterns, each with a short code snippet.
1. Infoboxes that pull data from page properties
Suppose you have a page that stores a population property. Your module can read that and format a table:
local p = {}
function p.infobox(frame)
local title = mw.title.getCurrentTitle()
local pop = title.properties.population or "unknown"
return string.format(
"'''%s'''Population: %s", title.text, pop
)
end
return pUsage on a page:
{{#invoke:MyInfobox|infobox}}2. Navigation boxes with dynamic links
Generating a list of sibling pages can be a pain in pure wikitext. Lua makes it painless:
local p = {}
function p.nav(frame)
local cat = frame.args[1] or "Category:Examples"
local pages = mw.title.makeTitle( nil, cat ):getCategoryMembers()
local out = {}
for _, page in ipairs(pages) do
table.insert(out, "[[" .. page.title .. "]]")
end
return table.concat(out, " • ")
end
return pAnd on a page you just write:
{{#invoke:NavHelper|nav|Category:Animals}}3. Simple math without leaving the page
Need to calculate a percentage? No problem:
local p = {}
function p.percent(frame)
local a = tonumber(frame.args[1]) or 0
local b = tonumber(frame.args[2]) or 1
if b == 0 then return "∞" end
return string.format("%.2f%%", (a/b)*100)
end
return pUse it like:
{{#invoke:Calc|percent|42|128}}Optional goodies – making the editing experience nicer
If you want a richer editor for your Lua modules (syntax highlighting, auto‑indent, etc.), consider adding one or more of the following extensions. They’re not required for Scribunto to work, but they feel like a fresh coat of paint over a solid foundation.
- WikiEditor – basic WYSIWYG editing, works out‑of‑the‑box.
- SyntaxHighlight – adds
<syntaxhighlight lang="lua">tags, handy for documentation.
CodeEditor – a full‑blown code editor with CodeMirror under the hood. Enable it for modules by setting:
$wgScribuntoUseCodeEditor = true;Configuration deep‑dive (the stuff that makes Scribunto tick)
Most admins never touch these, but if you need to tighten resources or change the engine, here’s the cheat‑sheet.
| Variable | Typical value | What it does |
|---|---|---|
$wgScribuntoDefaultEngine | 'luastandalone' (or 'luasandbox') | Chooses which engine runs your modules. luasandbox is a PHP‑based engine that can be faster but is not bundled by default. |
$wgScribuntoEngineConf['luastandalone']['luaPath'] | null (uses bundled binary) | Path to a custom Lua interpreter if you need one. |
$wgScribuntoEngineConf['luastandalone']['memoryLimit'] | 52428800 (≈ 50 MB) | Maximum memory a module may allocate. Adjust if you see “memory exhausted” errors. |
$wgScribuntoEngineConf['luastandalone']['cpuLimit'] | 7 seconds | CPU time limit per invocation. Raise carefully – you don’t want rogue scripts hogging the server. |
$wgScribuntoEngineConf['luastandalone']['errorFile'] | null | Where the interpreter writes stderr. Set to a writable log file to capture cryptic errors. |
Debugging – the “what‑the‑heck‑happened?” section
When a module throws an error you’ll see a big red “Lua error: …” message on the page. Here’s how to make sense of it.
- Enable the debug console. While editing a module, scroll down below the edit box – there’s a tiny “Debug console” textbox. Paste the problematic call (e.g.
{{#invoke:MyModule|myFunc}}) and hit “Run”. The console will show the raw Lua error plus a stack trace. - Check the error log. If you set
$wgScribuntoEngineConf['luastandalone']['errorFile'], open that file on the server. You’ll see the exact stdout/stderr from the Lua binary – invaluable for “internal error: interpreter exited with status 1”. - Common pitfalls.
- Missing
proc_open– if your PHPdisable_functionslist includesproc_open, Scribunto can’t spawn the Lua process. Remove it fromphp.inior ask your host. - Binary permissions. On Linux, the bundled
luamust be executable. Runchmod a+x extensions/Scribunto/includes/Engines/LuaStandalone/binaries/*/lua. - SELinux “noexec”. Some shared hosts mount
/var/wwwwithnoexec. Either move the binaries to an exec‑allowed location or disable the enforcement (if you control the server).
- Missing
Troubleshooting
Below is a short checklist you can copy‑paste into a wiki page for quick reference (feel free to adapt).
- “Script error” link is clickable – it opens a detailed stack trace.
- If you see “version ‘GLIBC_2.11’ not found”, you’re on an old Linux distro. Either upgrade the C library or download a custom Lua binary compiled against your system’s glibc.
- On ARM (Raspberry Pi) the default binary is for x86. Point
luaPathat/usr/bin/lua5.1(or build your own). The error message will mention “Intel 80386” if you’re using the wrong binary. - Memory‑limit errors appear as “Lua error: Internal error: The interpreter exited with status 2”. Bump
memoryLimitinLocalSettings.php. - “Internal error: status 126” usually means the binary isn’t executable or the filesystem blocks execution. Fix permissions or move the binary.
- Blank screen after enabling Scribunto? You’re probably on an outdated MediaWiki version that doesn’t match the extension’s release branch. Upgrade MediaWiki or roll back to a matching Scribunto tag.
Best practices – how to keep your Lua modules tidy and performant
- Keep functions small. A module with one monolithic function is harder to test and more likely to hit the CPU time limit.
- Cache expensive results. Use
mw.loadDataormw.smw.getQuery(if you have Semantic MediaWiki) and store results inmw.storefor subsequent calls. - Document your API. Add a
---comment block at the top of each module describing argument order; it helps future editors and tools likemw.toolforge.orgcan auto‑generate API docs. - Avoid global side‑effects. Since each
#invokegets a fresh sandbox, you might be tempted to use globals for “shared state”. That won’t work – use the storage APIs instead. - Unit‑test with the debug console. The console can be used as a REPL; write small test calls before you commit a big change.
Wrapping up
If you’ve made it this far, congratulations – you’ve gone from “I heard about Scribunto” to “I’m writing real Lua modules on my wiki”. The next steps are up to you: maybe start a community library of reusable modules, or contribute back to the upstream repo if you discover a bug or a clever pattern.
Remember, Scribunto isn’t a silver bullet; it shines when you need deterministic, sandboxed logic that lives close to the content. When you combine it with other extensions (like Capiunto for infoboxes, or Semantic Scribunto for data‑driven queries) you get a truly dynamic wiki without compromising security