Building Custom Widgets for MediaWiki Using the Widgets Extension

Why “Widgets” Even Matter in a MediaWiki World

Picture this: you’re editing a wiki page about a newly released indie game, and you want to embed a live‑updating score table, a tiny weather widget for the next in‑game event, or even a simple countdown to the next patch. Plain wikitext can handle static tables, sure, but the real‑time flair? That’s where the Widgets extension swoops in like a backstage crew.

In practice, “widgets” are basically sandboxed snippets of HTML, CSS, and JavaScript that you can drop into any wiki page via {{#widget:WidgetName}}. The extension runs them in a safe iframe, preventing rogue scripts from messing with the rest of the site. It’s a neat compromise between an open‑ended Extension Sandbox and the stricter TemplateStyles approach.

Getting the Widgets Extension Up and Running

First things first – you need the extension itself. Grab the latest release from the official page or, if you’re comfortable with Composer, just run:

composer require mediawiki/widgets

Then pop the files into extensions/Widgets and add the usual hook to LocalSettings.php:

$wgExtensionCredits['parserhook'][] = array(
    'path' => __DIR__ . '/Widgets',
    'name' => 'Widgets',
    'author' => 'Yaron Koren and others',
    'url' => 'https://www.mediawiki.org/wiki/Extension:Widgets',
    'descriptionmsg' => 'widgets-desc',
);
wfLoadExtension( 'Widgets' );

Don’t forget to run the update script afterwards – a quick php maintenance/update.php does the trick. If you see a warning about “sandboxedMode”, that’s a good sign: the extension is ready to keep your scripts in a virtual sandbox.

Permissions – Who Gets to Play?

By default, only users with the editinterface right can create or edit widgets. That’s sensible; after all, you don’t want every anonymous contributor uploading a JavaScript bot that steals cookies. If you need a more granular setup, tweak $wgGroupPermissions:

$wgGroupPermissions['autoconfirmed']['editwidgets'] = true;

Now any user who’s crossed the autoconfirmed threshold can touch widget pages – just remember to audit them from time to time.

Creating Your First Widget

Let’s dive in. In a fresh wiki install, go to Special:CreatePage (or simply type the title in the address bar) and create a page with the prefix Widget:. The title after the colon becomes the widget’s name – for example, Widget:HelloWorld.

Inside, you write regular HTML, optionally peppered with CSS and JavaScript. The extension silently strips out anything that looks like a security risk (like eval() or document.cookie), so keep it tidy.

Simple “Hello, World!” Example

<div class="hello-widget">
    <h3>Hello, World!</h3>
    <p>This is a minimal widget.</p>
</div>
<style>
    .hello-widget {
        background:#f0f8ff;
        border:1px solid #aac;
        padding:8px;
        font-family:Arial, sans-serif;
    }
</style>
<script>
    // A tiny script that toggles the message on click
    document.addEventListener('DOMContentLoaded', function() {
        const el = document.querySelector('.hello-widget h3');
        if (el) {
            el.addEventListener('click', () => el.textContent = 'Clicked!');
        }
    });
</script>

Save that page, then sprinkle the widget anywhere with {{#widget:HelloWorld}}. You’ll see the little box appear, and clicking the heading swaps the text. No extra template needed, no bloated markup – just pure, sandboxed interactivity.

Passing Parameters – Making Widgets Reusable

Hard‑coding everything is a bit like baking a cake once and then never being able to change the flavor. The Widgets extension lets you pass arguments, much like parser functions. Here’s how you add a msg parameter:

<div class="msg-widget">
    <h3><mw:variable name="msg">Default Message</mw:variable></h3>
</div>
<style>
    .msg-widget { color:#333; }
</style>

Now embed it like this:

{{#widget:MessageBox|msg=Welcome to the wiki!}}

The widget prints “Welcome to the wiki!” in the heading. If you omit the argument, it falls back to “Default Message”. Handy for quick alerts or info‑boxes that you want to reuse across dozens of pages.

Advanced Topics – The Nitty‑Gritty

Embedding External Resources

Sometimes you need a third‑party library – say Chart.js for a live chart. The extension respects $wgWidgetSources, which whitelists domains you can pull scripts or styles from. Add something like:

$wgWidgetSources = [
    'cdnjs.cloudflare.com',
    'cdn.jsdelivr.net',
];

Then in your widget:

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<canvas id="myChart"></canvas>
<script>
    const ctx = document.getElementById('myChart').getContext('2d');
    new Chart(ctx, {
        type: 'bar',
        data: { labels:['A','B','C'], datasets:[{label:'Score',data:[12,19,3]}] }
    });
</script>

Because the source is whitelisted, the script loads inside the widget’s sandbox. No need to edit the main site’s LocalSettings.php for each new library – just slap the domain into the array.

Styling Conflicts and Scoping

One pitfall many newcomers hit is “my widget’s CSS is bleeding into the page”. The extension automatically scopes styles using a unique wrapper ID, but only if you follow the pattern #widget-{id} .myclass. A quick trick is to wrap everything in a <div class="widget-inner"> and reference it relatively:

#widget-123 .widget-inner {
    /* all styles are safely confined */
}

If you forget that, you might see your page’s navigation turn neon pink – not ideal, unless that’s the vibe you’re aiming for. (I’ve seen it happen, and it’s a good reminder to keep CSS well‑scoped.)

Debugging Tips – Sandbox, Not a Black Box

  • Use the browser console. The widget runs inside an iframe, so open DevTools, select the frame, and you’ll see any JS errors.
  • Check the “widget preview”. Append ?action=render to the widget page URL (e.g., Widget:HelloWorld?action=render) to see raw output without the surrounding wiki layout.
  • Watch the log. The extension logs sanitization warnings to error.log – helpful when something gets stripped unexpectedly.

Best Practices – Making Widgets Maintainable

Building a widget for a single page is fine, but in a production wiki you’ll likely have dozens of them. Here are a few habits that keep the codebase sane:

  1. Name your widgets descriptively. Widget:RecentChangesChart beats Widget:Chart1 any day.
  2. Separate concerns. Stick to HTML in the widget page, keep heavy JavaScript in an external file (served from a whitelisted CDN) and reference it.
  3. Document parameters. Include a short comment at the top of the widget page like – it’s a tiny thing but saves future editors a lot of head‑scratching.
  4. Version your widgets. When you need to tweak behavior, clone the existing widget (e.g., Widget:RecentChangesChart_v2) and update the call sites. This avoids breaking pages that relied on older logic.
  5. Test on multiple skins. MediaWiki can be dressed up in Vector, MonoBook, or even custom skins. A widget that looks perfect in Vector might have clipping issues in Timeless – a quick visual check helps.

Real‑World Use Cases – A Quick Tour

To illustrate the flexibility, here are three snippets that showcase different scenarios.

1. Live Event Countdown (Uses setInterval)

<div id="countdown" class="countdown-widget">Loading…</div>
<script>
    const target = new Date('2025-12-31T23:59:59Z').getTime();
    function update() {
        const now = Date.now();
        const diff = target - now;
        if (diff <= 0) {
            document.getElementById('countdown').textContent = 'Event started!';
            clearInterval(timer);
            return;
        }
        const hrs = Math.floor(diff/36e5);
        const mins = Math.floor(diff%36e5/6e4);
        const secs = Math.floor(diff%6e4/1e3);
        document.getElementById('countdown').textContent = `${hrs}h ${mins}m ${secs}s`;
    }
    const timer = setInterval(update, 1000);
    update();
</script>
<style>
    .countdown-widget { font-weight:bold; color:#c33; }
</style>

This widget can be dropped onto a community page announcing a game launch, and it updates in real time for every visitor.

2. Interactive Image Map (Leverages CSS Grid)

<div class="map-grid">
    <img src="/images/continent.png" alt="Map" style="grid-area:1/1/2/2;">
    <button style="grid-area:1/1/2/2;" class="region" data-name="North">North</button>
    <button style="grid-area:1/1/2/2;" class="region" data-name="South">South</button>
</div>
<script>
    document.querySelectorAll('.region').forEach(btn => {
        btn.addEventListener('click', () => {
            alert('You clicked ' + btn.dataset.name);
        });
    });
</script>
<style>
    .map-grid { display:grid; position:relative; }
    .region {
        background:transparent;
        border:none;
        color:transparent;
        cursor:pointer;
    }
</style>

That tiny map lets users click invisible hotspots, perfect for a geography wiki that wants a bit of flair without loading a full‑blown JavaScript library.

3. Server‑Side Data Pull (Uses action=raw)

Sometimes you need dynamic data that lives in another wiki page – like a list of upcoming meetings stored in Template:MeetingSchedule. The widget can fetch that content via an Ajax call to the same server:

<div id="schedule">Fetching schedule…</div>
<script>
    fetch('/index.php?title=Template:MeetingSchedule&action=raw')
        .then(r => r.text())
        .then(text => {
            document.getElementById('schedule').innerHTML = text;
        })
        .catch(() => {
            document.getElementById('schedule').textContent = 'Error loading schedule';
        });
</script>

The widget stays lightweight – no need for a full parser function – and you get live updates whenever the template changes.

Wrapping Up (Without the Classic “In Conclusion”)

Building custom widgets for MediaWiki isn’t a magic trick; it’s more like a craft workshop. You set up the sandbox, carve out HTML, sprinkle in CSS, and give it a dash of JavaScript, all while the extension keeps an eye on security. The reward? Pages that feel alive, data that updates without a full page refresh, and a wiki that can serve as a lightweight web app.

If you ever find yourself staring at a blank widget page, think about the end‑user experience – are you solving a real‑world problem or just showing off a pretty chart? A little restraint goes a long way, especially when the same widget will be reused across dozens of articles.

Happy widget‑making!

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