Building Custom Widgets for MediaWiki Using the Widgets Extension

Why “Widgets” Even Matter in a MediaWiki World

The Widgets extension is useful when a normal template is not enough. It lets you keep a small piece of HTML, CSS, or JavaScript in the Widget: namespace and call it from a wiki page with {{#widget:Name}}.

That power comes with a trade-off. A widget is not just wikitext with nicer syntax. It can output raw HTML, and it can run client-side code. Treat widget pages like interface code: keep edit access tight, review changes, and escape any value that comes from a page author.

The official entry point is the Widgets extension page. For comparison, TemplateStyles is usually the better choice when you only need per-template CSS. Use Widgets when you need markup or browser-side behavior that TemplateStyles cannot provide.

Installing Widgets

Install the extension the same way you would install most MediaWiki extensions: put it under extensions/Widgets, install its PHP dependencies, then load it from LocalSettings.php. The current extension page lists MediaWiki 1.38+ support and version 1.7.0, so check compatibility before dropping it into an older wiki.

cd extensions
git clone https://gerrit.wikimedia.org/r/mediawiki/extensions/Widgets.git
cd Widgets
composer update --no-dev

Then add this to LocalSettings.php:

wfLoadExtension( 'Widgets' );

Widgets uses a compiled template directory. Make sure the directory exists and is writable by the web server user. If you move it, set $wgWidgetsCompileDir and keep the path unique per wiki, especially on farms or multi-wiki installs.

// Keep compiled widget templates outside shared writable paths.
$wgWidgetsCompileDir = "$IP/extensions/Widgets/compiled_templates/";

Permissions first

The extension adds the editwidgets right and creates a widgeteditor group. Keep that group small. Anyone who can edit widget pages can affect rendered pages across the site, so this is closer to granting interface-editing access than granting normal page-editing access.

A small private wiki may give the right to sysops only. A larger wiki may use a dedicated group:

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

If you do open widget editing to trusted non-admins, add review habits around it: recent changes watchlists, FlaggedRevs if your wiki already uses it, and a simple rule that widget changes should explain why the script or markup changed.

Creating a widget

Create a page in the Widget: namespace. The page title becomes the widget name. For example, Widget:NoticeBox can be rendered with:

{{#widget:NoticeBox}}

Widget pages use Smarty syntax for parameters. A practical first widget is a reusable notice box:

<includeonly>
<div class="notice-box">
    <strong><!--{$title|default:'Note'|escape:'html'}--></strong>
    <p><!--{$message|default:'No message provided.'|escape:'html'}--></p>
</div>

<style>
/* Scoped class name; keep widget CSS boring and predictable. */
.notice-box {
    border: 1px solid #a2a9b1;
    background: #f8f9fa;
    padding: 0.75rem;
    margin: 1rem 0;
}
.notice-box strong {
    display: block;
    margin-bottom: 0.25rem;
}
</style>
</includeonly>

Call it like this:

{{#widget:NoticeBox
 |title=Maintenance window
 |message=Uploads will be disabled for about 20 minutes.
}}

The important part is not the box. It is the escaping. Use escape:'html', escape:'url', escape:'urlpathinfo', or escape:'javascript' depending on where the value is inserted. Do not pass page-supplied values straight into HTML attributes, scripts, or URLs.

Parameters and defaults

Widgets are most useful when the widget page contains the structure and the wiki page supplies the data. Keep parameter names explicit. message, linkUrl, and linkText age better than text1 and text2.

Here is a small link widget that validates the URL and escapes both values:

<includeonly>
<a class="external-resource"
   href="<!--{$url|validate:url|escape:'html'}-->">
    <!--{$label|default:'Open link'|escape:'html'}-->
</a>
</includeonly>

And the call site:

{{#widget:ExternalResource
 |url=https://example.org/report
 |label=Quarterly report
}}

Validation is not a substitute for escaping. Use both when a parameter becomes a URL, an attribute, or JavaScript data.

Using JavaScript

A widget can include JavaScript, but keep it small. If the script grows past a few lines, move the hard part to a reviewed file or a trusted CDN and keep the widget page as the integration point. Pin CDN versions in production instead of loading whatever is latest that day.

Example with Chart.js:

<includeonly>
<canvas id="status-chart" width="400" height="180"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// Small inline glue only; keep larger chart code in reviewed assets.
const canvas = document.getElementById('status-chart');
if (canvas) {
    new Chart(canvas, {
        type: 'bar',
        data: {
            labels: ['Open', 'In review', 'Done'],
            datasets: [{
                label: 'Tasks',
                data: [12, 5, 18]
            }]
        }
    });
}
</script>
</includeonly>

Before using a third-party script, decide who owns the risk. A CDN URL is still code execution in readers' browsers. Use a stable source, pin the version where practical, and avoid adding libraries for simple behavior that plain JavaScript can handle.

Styling without surprises

Widget CSS can clash with the skin or with other page content. Use a wrapper class and keep selectors narrow. Avoid generic selectors like button, table, or .title unless the widget owns everything inside the wrapper.

.task-widget {
    border-collapse: collapse;
    width: 100%;
}
.task-widget th,
.task-widget td {
    border: 1px solid #a2a9b1;
    padding: 0.4rem 0.5rem;
}

Test widgets in the skins your wiki actually uses. Vector and Timeless do not always expose layout bugs the same way, and custom skins are even less forgiving.

Debugging

Most widget problems are simple once you know where to look:

  • Nothing renders: confirm the widget page name matches the parser function call. Widget names are case-sensitive.
  • New code does not show: purge the widget page or the page that embeds it. Widget output can be cached.
  • Server error mentions Smarty compile dir: check ownership and write permissions for the compiled templates directory.
  • Parameter output looks wrong: check your Smarty syntax and escaping modifier.
  • JavaScript fails: open browser developer tools and check the console on the rendered page.

Maintenance rules

A few habits keep widgets manageable:

  1. Name widgets after the job they do. Widget:ReleaseCountdown is better than Widget:Script2.
  2. Document parameters at the top of the widget page. A short HTML comment is enough.
  3. Escape every parameter. Make this review policy, not personal preference.
  4. Keep widget pages short. If the page becomes a small application, split the reviewed JavaScript or CSS out of the widget.
  5. Review external assets. Do not let old CDN links and unmaintained libraries become invisible dependencies.

Widgets are a good fit for small interactive pieces: a status panel, a chart, an embedded tool, a countdown, a controlled iframe, or markup that would be painful in wikitext. They are not a shortcut around MediaWiki's security model. Used carefully, they give editors a clean way to reuse richer page components without turning every article into hand-maintained HTML.

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