Enhancing MediaWiki with Custom JavaScript Gadgets

Why bother with a little JavaScript magic?

Picture this: you’ve just logged into your favourite wiki, eyes scanning the sea of articles, and—boom!—a tiny button appears next to each heading, letting you copy a section title with a single click. No, it’s not sorcery; it’s a gadget you wrote yourself.

Gadgets are the under‑the‑hood “personalisation” layer of MediaWiki. They let you sprinkle a dash of JavaScript (or CSS) onto the interface without touching core code. In other words, you get the power of a browser extension, but it lives inside the wiki and can be toggled on or off by any user who wants it.

Since MediaWiki 1.41 landed in August 2025, the gadget kitchen has been revamped to make loading faster and conflicts rarer. So, if you’ve ever thought “I wish my wiki could do that”, read on. I’ll walk you through the whole shebang—some best‑practices, a few gotchas, and a couple of snippets that actually work.

Getting the kitchen ready

  • Step 1: Make sure the Extension:Gadgets is enabled. In LocalSettings.php you should see something like wfLoadExtension( 'Gadgets' );. If not, add it and clear the cache.
  • Step 2: Create the page MediaWiki:Gadgets-definition. This is where you declare what scripts (or styles) belong to each gadget.
  • Step 3: Write the JavaScript file itself, usually under MediaWiki:Gadget‑MyCoolFeature.js. You can also host the file on a regular wiki page, but the MediaWiki: namespace keeps it tidy.

That’s it—technically. In practice you’ll want to test, debug, and maybe even add a bit of localisation, but the foundation is this trio.

A quick‑look at a real‑world example

Let’s say you want a “copy‑link” button that appears next to each <h2> heading. Here’s a minimal gadget definition and script.


## MediaWiki:Gadgets-definition
# CopyLinkButton.js|default|rights=edit

Now the JavaScript. I’ll keep it short, but sprinkle in a few comments because, well, I tend to over‑comment.


// MediaWiki:Gadget-CopyLinkButton.js
// Add a tiny clipboard icon after each h2 heading
mw.loader.using( 'mediawiki.util' ).then( function () {
    // Grab all h2s that are not inside tables (just in case)
    var headings = document.querySelectorAll( 'h2:not(table h2)' );
    headings.forEach( function ( h ) {
        // Create the button element
        var btn = document.createElement( 'button' );
        btn.textContent = '📋';
        btn.title = 'Copy link to this section';
        btn.style.marginLeft = '0.3em';
        btn.className = 'copy-link-btn mw-ui-button';

        // When clicked, copy the anchor link to clipboard
        btn.addEventListener( 'click', function ( e ) {
            e.preventDefault();
            var link = location.origin + location.pathname + '#'+ h.id;
            navigator.clipboard.writeText( link ).then( function () {
                // Light‑up feedback – a quick flash
                btn.style.background = '#8f8';
                setTimeout( function () { btn.style.background = ''; }, 300 );
            } );
        } );

        h.appendChild( btn );
    } );
} );

Notice how the script waits for mediawiki.util to load before doing anything. That’s a pattern the ResourceLoader docs stress: you don’t want your gadget to race ahead of core modules.

Hooking into user preferences

When you add a gadget definition, it automatically shows up on Special:Preferences → Gadgets. Users can tick the box, and MediaWiki will sprinkle the script into the page via ResourceLoader. No need to edit LocalSettings.php for each user.

But what if you want a gadget that’s always on for everyone—maybe a navigation helper for a corporate wiki? Add default=1 to the definition line, like so:


# MyNavHelper.js|default=1|rights=edit

That tiny default=1 flag is the difference between “optional” and “built‑in”. Keep in mind, though, that forced gadgets should be lightweight; you don’t want to slow down every visitor’s first page view.

Best‑practice buffet (served with a side of anecdotes)

  • Keep it modular. Split big scripts into logical chunks and load them with mw.loader.load only when needed. In the copy‑link example we could have a separate module for the UI and another for the clipboard logic.
  • Mind the namespace. Use mw prefixes (e.g., mw.config.get) instead of global variables. One time I accidentally overwrote window.$ with a custom function, and the whole wiki’s jQuery stopped working. Oops.
  • Respect localisation. Pull strings via mw.message and provide i18n files. Even a simple btn.title can be translated for non‑English wikis.
  • Test on a sandbox. MediaWiki ships with Special:Sandbox but for gadgets I usually spin up a test wiki on Developer Sandbox. It’s a safe place to break things without upsetting regular users.
  • Audit performance. Open the browser’s DevTools → Network tab, filter for “ext.gadget”. If a gadget pulls in a 200 KB library, consider lazy‑loading or trimming it down.

Debugging without losing your mind

When a gadget misbehaves, the first thing I do is open the console and type mw.loader.using('ext.gadget.MyCoolFeature'). If the promise rejects, there’s a loading issue. If it resolves, the problem is likely in the script itself.

Another handy trick: sprinkle console.log statements with a unique prefix, like ‘[CopyLink]’. That way you can filter the noise in the console and not accidentally read “Uncaught TypeError” from some unrelated core script.

And don’t forget the debug=true URL parameter. Adding ?debug=true after a page URL forces MediaWiki to load uncompressed ResourceLoader modules, making stack traces far more readable.

Real‑world tweaks you might actually need

Below are a few snippets pulled from my own wikis—some were born out of frustration, others out of pure curiosity. Feel free to copy, adapt, or just marvel at the oddities.


// 1️⃣ Show a tiny “beta” badge next to a template that’s still under development
mw.loader.using( 'mediawiki.util' ).then( function () {
    var betaTemplates = document.querySelectorAll( '.mw-parser-output .template-badge-beta' );
    betaTemplates.forEach( function ( el ) {
        el.title = 'This template is currently in beta';
        el.style.border = '1px dashed #c00';
        el.style.padding = '2px';
    } );
} );

// 2️⃣ Auto‑expand collapsed sections on mobile devices (because who likes tapping “show more” repeatedly?)
if ( mw.config.get( 'wgDevice' ) === 'mobile' ) {
    document.querySelectorAll( '.mw-collapsible' ).forEach( function ( div ) {
        if ( div.classList.contains( 'mw-collapsed' ) ) {
            div.classList.remove( 'mw-collapsed' );
        }
    } );
}

Both of those are tiny, but they illustrate a bigger point: gadgets can be as specific as you need them to be. One wiki might need a “beta badge”, another might need “auto‑expand on mobile”. The beauty is that each user can decide whether to enable them.

Security—don’t let your gadgets become a back‑door

JavaScript runs in the browser, so it can’t directly touch the server’s file system. Still, a rogue script could read cookies, hijack forms, or even inject malicious HTML. The rights= flag in the gadget definition is your first line of defence: set it to edit if you only want logged‑in editors to use the gadget, or read for anyone.

MediaWiki also sanitises user‑generated content via DOMPurify (since 1.40). If you’re building a gadget that constructs HTML from user input, run it through mw.html.safe or DOMPurify.sanitize first. Trust me, you don’t want to be the headline of a security advisory.

Going beyond: mixing gadgets with user scripts

Some wikis separate “official” gadgets (managed by admins) from “personal” user scripts (stored under User:Username/common.js). The line can blur. If you’re prototyping, start in your own common.js—you can iterate faster. Once it’s stable, migrate it to MediaWiki:Gadget‑YourFeature.js so other editors can enable it via preferences.

A word of caution: user scripts run before gadgets, so don’t rely on a gadget’s variables being present in a user script. I once tried to call mw.myGadgetFunction from common.js and got “undefined”. The fix? Move the call into a mw.loader.using('ext.gadget.YourFeature') block.

Wrapping up the chaos (but not in a “conclusion” way)

At the end of the day, gadgets are just JavaScript that MediaWiki hands to the browser in a neat, toggleable package. They empower you to tailor the wiki experience without touching the server code, and they let each user decide what “extra sauce” they want on their page.

If you’re reading this on a wiki that already has a copy‑link button, kudos—you’ve already seen a gadget in action. If not, maybe it’s time to give one a try. Just remember: start small, test often, and keep an eye on performance. The next time you wander through a sea of articles, you might just notice a tiny new feature you didn’t know you needed.

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