Building Custom Widgets for MediaWiki Using the Widgets Extension
A practical guide to the Widgets extension: what it is, when to prefer TemplateStyles, how to install and secure it, and how to write widgets that escape properly.
The Widgets extension is the right tool 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 any wiki page with {{#widget:Name}}. The current release (1.7.1, May 2026) supports MediaWiki 1.38 and later, so check compatibility before installing on an older wiki.
That power comes with a trade-off. A widget is not just wikitext with nicer syntax: it can output raw HTML and 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. 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 usual way: put it under extensions/Widgets, install its PHP dependencies with Composer, then load it:
cd extensions
git clone https://gerrit.wikimedia.org/r/mediawiki/extensions/Widgets.git
cd Widgets
composer update --no-dev
wfLoadExtension( 'Widgets' );
Widgets compiles templates to a directory at runtime. 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 wiki farms:
// 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 whole site, so this is closer to interface-editing access than 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: watch RecentChanges, use FlaggedRevs if your wiki already runs it, and require that widget changes explain why the markup or script changed.
Creating a widget
Create a page in the Widget: namespace; the page title becomes the widget name. For example, Widget:NoticeBox is 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>
/* Keep widget CSS scoped 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. Never pass page-supplied values straight into HTML attributes, scripts or URLs.
Parameters and defaults
Widgets are most useful when the widget page holds the structure and the wiki page supplies the data. Keep parameter names explicit — message, linkUrl, 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>
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. A widget with Chart.js looks like this:
<includeonly>
<canvas id="status-chart" width="400" height="180"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
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 a library when plain JavaScript is enough.
Styling without surprises
Widget CSS can clash with the skin or 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 less forgiving.
Debugging
- 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 the Smarty compile dir — check ownership and write permissions on the compiled templates directory
- Parameter output looks wrong — check the Smarty syntax and the escaping modifier
- JavaScript fails — open the browser developer tools and check the console on the rendered page
Maintenance rules
- Name widgets after the job they do —
Widget:ReleaseCountdownbeatsWidget:Script2 - Document parameters at the top of the widget page — a short HTML comment is enough
- Escape every parameter — make this review policy, not personal preference
- Keep widget pages short — once the page becomes a small application, split the reviewed JavaScript or CSS out of it
- Review external assets — do not let old CDN links and unmaintained libraries become invisible dependencies
Widgets fit small interactive pieces well: status panels, charts, countdowns, controlled iframes, 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. Pre-built widgets are also available from the MediaWikiWidgets.org catalog.