Skip to content

MediaWiki Scribunto & Lua — Lua Modules, Scribunto Scripting, and Dynamic Templates

DodaTech Updated 2026-06-26 10 min read

In this tutorial, you will learn about MediaWiki Scribunto & Lua. We cover key concepts, practical examples, and best practices to help you master this topic.

Scribunto and Lua in MediaWiki let you write programmable templates using the Lua scripting language — creating modules that handle complex logic, string manipulation, data processing, and dynamic content generation beyond what wikitext parser functions can do, just as Wikipedia uses Lua for its most sophisticated infoboxes and navigation systems.

What You'll Learn

  • Installing the Scribunto extension
  • Creating Lua modules in the Module namespace
  • Writing functions that accept template parameters
  • Using Lua libraries for string, math, and table operations
  • Calling Lua modules from wiki templates
  • Debugging Lua modules

Why It Matters

Parser functions (#if, #switch, #expr) handle basic logic, but they become unreadable and slow with complex operations. Lua is a real programming language. It handles loops, data structures, file operations, and complex string processing efficiently. A single Lua module can replace 50 lines of nested parser functions with 10 lines of clean Lua code. Scribunto modules load faster, are easier to debug, and produce more maintainable templates.

Real-World Use

A DodaTech wiki uses a Lua module to format version numbers. The module accepts "210" and outputs "2.1.0", accepts "31415" and outputs "3.14.15", and handles special cases like "100" becoming "1.0.0". Another module maintains a lookup table of product codenames. A third module generates breadcrumb navigation from the page title hierarchy. All three would be impractical with parser functions alone.

Learning Path

flowchart LR
  A["26: Semantic MediaWiki"] --> B["27: VisualEditor"]
  B --> C["28: Scribunto & Lua"]
  C:::current
  D["29: Cite & References"]
  E["30: Interwiki Links"]
  F["31: Wiki Farms"]

  C --> D --> E --> F

  classDef current fill#38bdf8,color#0f172a,stroke-width:2px

Step 1: Install Scribunto

cd /opt/lampp/htdocs/mediawiki/extensions
git clone https://gerrit.wikimedia.org/r/mediawiki/extensions/Scribunto.git
cd Scribunto
git checkout REL1_42

Enable in LocalSettings.php:

wfLoadExtension( 'Scribunto' );

Scribunto has no configuration options. It works immediately after enabling. Verify it appears on Special:Version.

Lua Binary

Scribunto uses the Lua language. It requires a Lua Interpreter:

# Install Lua
apt install lua5.1

# Or compile from source
wget https://www.lua.org/ftp/lua-5.1.5.tar.gz
tar -xzf lua-5.1.5.tar.gz
cd lua-5.1.5
make linux
make install

Scribunto also includes a sandboxed PHP-based Lua interpreter as fallback.

Step 2: Understand the Module Namespace

Lua modules live in the Module: namespace. Each module is a Lua script that returns a table of functions.

Module Structure

= Module:Hello =

local p = {}

function p.hello( frame )
    return "Hello, world!"
end

function p.greet( frame )
    local name = frame.args[1] or "World"
    return "Hello, " .. name .. "!"
end

return p

Key points:

  • p is a table that holds the module's functions
  • Each function receives a frame object with parameter access
  • frame.args[1] accesses the first unnamed parameter
  • frame.args["name"] accesses a named parameter
  • The module must return the table p

Step 3: Call Lua from Templates

Create a template that invokes the Lua module:

= Template:Hello =

{{#invoke:Hello|hello}}
= Template:Greet =

{{#invoke:Hello|greet|{{{1|World}}}}}

Usage on a page:

{{Hello}}           Renders: Hello, world!
{{Greet|Alice}}     Renders: Hello, Alice!
{{Greet}}           Renders: Hello, World!

The #invoke parser function calls a Lua module. The syntax is {{#invoke:ModuleName|FunctionName|param1|param2|...}}.

Step 4: Access Wiki Data from Lua

Lua modules can access wiki pages, categories, and templates.

Getting Page Content

local p = {}

function p.getPageContent( frame )
    local title = frame.args[1] or "Main Page"
    local page = mw.title.new( title )
    if page.exists then
        return "Title: " .. page.fullText
    else
        return "Page '" .. title .. "' does not exist."
    end
end

function p.getPageText( frame )
    local title = frame.args[1] or "Main Page"
    local page = mw.title.new( title )
    if page.exists then
        return page:getContent()
    else
        return ""
    end
end

return p

Site Information

local p = {}

function p.siteInfo( frame )
    local siteName = mw.site.siteName
    local currentTime = os.date( "%Y-%m-%d %H:%M:%S" )
    local pageCount = mw.site.stats.pagesInNamespace( 0 )
    local userCount = mw.site.stats.numberUsers

    return string.format(
        "%s — %s — %d pages — %d users",
        siteName, currentTime, pageCount, userCount
    )
end

return p

Step 5: String and Math Operations

Lua's string library is more powerful than wikitext parser functions.

String Formatting

local p = {}

function p.formatVersion( frame )
    local version = frame.args[1] or "100"
    -- Convert "210" to "2.1.0"
    local parts = {}
    for i = 1, #version do
        table.insert( parts, version:sub( i, i ) )
    end
    return table.concat( parts, "." )
end

function p.truncate( frame )
    local text = frame.args[1] or ""
    local maxLen = tonumber( frame.args[2] ) or 100
    if #text > maxLen then
        return text:sub( 1, maxLen ) .. "..."
    end
    return text
end

return p

Mathematical Operations

local p = {}

function p.fileSize( frame )
    local bytes = tonumber( frame.args[1] ) or 0
    local units = { "B", "KB", "MB", "GB", "TB" }
    local unitIndex = 1

    while bytes >= 1024 and unitIndex < #units do
        bytes = bytes / 1024
        unitIndex = unitIndex + 1
    end

    return string.format( "%.1f %s", bytes, units[unitIndex] )
end

function p.percentage( frame )
    local value = tonumber( frame.args[1] ) or 0
    local total = tonumber( frame.args[2] ) or 1
    if total == 0 then return "0%" end
    local percent = ( value / total ) * 100
    return string.format( "%.1f%%", percent )
end

return p

Step 6: Working with Tables

Tables are Lua's primary data structure. Use them for lookup tables and data processing.

Lookup Table

local p = {}

local productData = {
    ["DodaBrowser"] = { version = "5.2", released = "2026-03-15", status = "Stable" },
    ["DodaSync"] = { version = "2.1", released = "2026-01-20", status = "Stable" },
    ["DodaZIP"] = { version = "3.0", released = "2025-11-01", status = "Beta" },
    ["Durga AV"] = { version = "4.8", released = "2026-06-01", status = "Stable" },
}

function p.getProductInfo( frame )
    local product = frame.args[1] or ""
    local info = productData[product]

    if not info then
        return "Product '" .. product .. "' not found."
    end

    return string.format(
        "%s: v%s (%s) — %s",
        product, info.version, info.released, info.status
    )
end

function p.listAllProducts( frame )
    local results = {}
    for name, data in pairs( productData ) do
        table.insert( results, string.format(
            "* %s — v%s (%s)",
            name, data.version, data.status
        ) )
    end
    table.sort( results )
    return table.concat( results, "\n" )
end

return p

Step 7: Debugging Lua Modules

Add temporary debug output:

function p.debug( frame )
    local params = {}
    for key, value in pairs( frame.args ) do
        table.insert( params, key .. " = " .. value )
    end
    return "Parameters received:\n" .. table.concat( params, "\n" )
end

Using mw.log

The mw.log() function writes to the debug log:

function p.test( frame )
    mw.log( "Test function called" )
    mw.logObject( frame.args, "Args" )
    return "Check the debug log"
end

View the log in Special:Log/scribunto.

Common Errors

Lua error: attempt to index a nil value
  → You tried to access something that doesn't exist (e.g., frame.args[1] when no parameter was provided)

Lua error: attempt to call a nil value (method '...')
  → You misspelled a function name or the library is not available

Lua error: loop or previous error loading module
  → Your module has a syntax error. Check for missing 'end' or 'then'.

Step 8: Lua Module Best Practices

Keep Modules Focused

One module per functional area. Do not put all functions in one massive module.

Use Local Functions

Keep helper functions private (local):

local function sanitize( text )
    return text:gsub( "%s+", " " ):gsub( "^%s*(.-)%s*$", "%1" )
end

function p.safeGreet( frame )
    local name = sanitize( frame.args[1] or "World" )
    return "Hello, " .. name .. "!"
end

Cache Expensive Operations

Store results of expensive operations:

local siteName = nil

function p.getSiteName( frame )
    if not siteName then
        siteName = mw.site.siteName
    end
    return siteName
end

Use the mw.html Library

Build structured HTML output safely:

function p.buildInfobox( frame )
    local div = mw.html.create( "div" )
        :addClass( "infobox" )
        :css( "border", "1px solid #ccc" )
        :css( "padding", "10px" )
        :wikitext( frame.args[1] or "" )
    return tostring( div )
end

What You Learned

  • Scribunto enables Lua scripting for MediaWiki templates
  • Modules are stored in the Module: namespace
  • {{#invoke:Module|Function|params}} calls Lua from wiki pages
  • frame.args provides access to template parameters
  • Lua libraries handle string, math, table, and HTML operations
  • mw.title and mw.site access wiki data
  • Debugging uses mw.log() and the scribunto log

In the next lesson, you'll learn about citations and references.

Common Mistakes

Mistake Why It Happens How to Fix
"Lua error: Script error" Syntax error in Lua code Check for missing end, then, or function keywords. Use mw.log() to trace execution.
Module not found error Wrong module name in #invoke Verify the module page exists at Module:Name. The name is case-sensitive. Check for typos.
Parameters not received Wrong frame.args access Parameters from the template call go to frame.args. Named parameters use frame.args["name"]. Positional parameters use frame.args[1], frame.args[2], etc.
Lua is slower than wikitext Inefficient Lua code Avoid global variables. Cache results of mw.title.new(). Use string manipulation efficiently.
Security warning in Lua module Unsafe operations detected Scribunto sandboxes Lua for security. You cannot access the filesystem, network, or execute shell commands. Use wiki-specific APIs instead.

Practice Questions

  1. How do you call a Lua module function from a wiki page, and how are parameters passed?
  2. Write a Lua module that takes a birth date and calculates the person's age in years.
  3. What is the purpose of the frame object in Scribunto functions?
  4. Challenge: Build a complete product information system with Lua. Create a module called ProductDB with a lookup table of 5 DodaTech products (name, version, release date, description). Write functions that: (a) display a single product's details, (b) list all products in a formatted table, (c) filter products by status (Stable/Beta/Alpha), (d) format version numbers consistently. Create template wrappers for each function so wiki editors can use them with simple {{ProductInfo|ProductName}} syntax. Create a "Products" page that uses the module to display all products in a dynamic table.

FAQ

What version of Lua does Scribunto use?

Scribunto uses Lua 5.1 with some features from Lua 5.2 and 5.3. It is sandboxed for security. Standard Lua libraries like string, table, math, and os (limited) are available.

Can Lua modules access the database?

No. Scribunto runs in a sandboxed environment. It cannot make database queries, network requests, or filesystem operations. Use the MediaWiki API or PHP extensions for these tasks.

Is Scribunto compatible with Semantic MediaWiki?

Yes. Lua modules can access semantic data using mw.smw module (if installed). However, direct SPARQL queries from Lua are not supported.

Can I use external Lua libraries?

Only pure Lua libraries that do not require C bindings. Scribunto includes the Lua FileSystem library is not available. You can include other modules using the require() function for modules defined on the wiki.

How do I test Lua modules without editing pages?

Create a sandbox page at Module:MyModule/Sandbox. Test there first. Use the frame:callParserFunction method to test interactions with wiki content without publishing.

Mini Project

Goal: Build a complete Lua-powered template system for a product wiki.

  1. Create a Lua module ProductData with a lookup table of 5 products
  2. Create a function that returns a formatted product card with HTML
  3. Create a function that lists all products in a sortable table
  4. Create a function that searches products by keyword
  5. Create template wrappers for each function
  6. Add input validation (handle missing parameters, invalid products)
  7. Add a debug function that shows all received parameters
  8. Create a test page that uses all functions
  9. Compare the Lua module's performance against an equivalent parser-function implementation

What's Next

Lua scripts make your templates powerful. Now let's add proper citations and references for credible documentation.

Continue to Lesson 29: Cite & References — learn about footnotes, citation templates, and the CiteThisPage extension.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro