Building Custom Skins for MediaWiki: A Developer Guide

Why bother with a custom skin?

When you first log into a MediaWiki site the interface looks like the familiar Vector theme. That’s fine for most readers, but developers often want a look that matches a brand, a conference, or just the itch to experiment with HTML/CSS. A custom skin is not a gimmick; it lets you control the entire body markup, apply your own colour palette, and even expose new UI hooks for extensions.

Getting your bearings

Before you dive in, make sure a working MediaWiki installation is up and running. Anything from 1.35 onward ships with the Mustache‑based skin framework, so you won’t need to wrestle with the old SkinTemplate PHP hierarchy unless you’re maintaining legacy skins.

  • Familiarity with CSS, JavaScript and JSON is enough.
  • If you can read a .less file, you’ll feel right at home.
  • PHP is nice to know, but not strictly required for the basics.

Folder structure – the skeleton

All skins live inside the skins/ directory of your MediaWiki installation. Create a folder named after your skin, for example MyCoolSkin. Inside, you’ll typically have:


MyCoolSkin/
│   extension.json          # skin registration
│   MyCoolSkin.mustache     # base html template
│   skin.less               # styling (can be .css)
│   skin.js                 # client‑side behaviour
│   i18n/
│       en.json
│       de.json
└── images/
    └── logo.svg

Registering the skin

MediaWiki discovers skins via the extension.json manifest. Here’s a minimal example. Note the use of commas – a stray one will break the parser, so double‑check.

{
    "name": "MyCoolSkin",
    "author": "Jane Doe",
    "url": "https://example.org/MyCoolSkin",
    "descriptionmsg": "mycoolskin-desc",
    "version": "1.0.0",
    "type": "skin",
    "manifest_version": 2,
    "requires": {
        "MediaWiki": ">= 1.35"
    },
    "AutoloadClasses": {
        "MyCoolSkin": "MyCoolSkin.skin.php"
    },
    "MessagesDirs": {
        "MyCoolSkin": [
            "i18n"
        ]
    },
    "SkinStyles": {
        "MyCoolSkin": [
            "skin.less"
        ]
    },
    "SkinScripts": {
        "MyCoolSkin": [
            "skin.js"
        ]
    }
}

After you drop the file in place, add a single line to LocalSettings.php:

wfLoadSkin( 'MyCoolSkin' );

If everything is wired correctly the new skin will appear under Special:Preferences → Appearance.

Mustache templates – the heart of the UI

Mustache is a logic‑less templating language. The skin’s .mustache file defines the <body> structure; the framework injects data for you. A tiny example:

{{! MyCoolSkin.mustache }}
<div id="siteNotice">{{{ siteNotice }}}</div>
<header id="my-header">
  <h1><a href="{{{ wgServer }}}">{{{ wgSitename }}}</a></h1>
  {{#isLoggedIn}}
    <nav>{{#personalTools}}<a href="{{ url }}">{{ title }}</a>{{/personalTools}}</nav>
  {{/isLoggedIn}}
</header>
<main id="content">
  {{{ content }}}
</main>
<footer>
  {{#footerLinks}}
    <a href="{{ url }}">{{ title }}</a>
  {{/footerLinks}}
</footer>

Key points:

  • {{{ variable }}} outputs raw HTML, useful for content and siteNotice.
  • {{#condition}} … {{/condition}} renders the block only when the boolean is true (e.g., isLoggedIn).
  • Arrays like personalTools are iterated automatically.

Internationalisation (i18n)

Every user‑visible string should be extracted to JSON files inside the i18n/ folder. The default language is en.json. For example:

{
    "mycoolskin-desc": "A bright, minimal skin for MediaWiki.",
    "mycoolskin-hello": "Welcome, $1!"
}

Within the Mustache template you can reference them with {{- mycoolskin-hello }}. The dash tells the parser to look up the message key.

Styling – Less, CSS variables, dark mode

Starting with MediaWiki 1.35 you can ship a .less file that automatically gets compiled. Use the built‑in variables to keep things consistent with the rest of the platform.

// skin.less
@import "mediawiki.skin.variables.less";

#my-header {
  background: @background-color-base;
  color: @color-primary;
  padding: 1rem;
}

@media (prefers-color-scheme: dark) {
  #my-header {
    background: @background-color-dark;
  }
}

/* RTL support */
html[dir="rtl"] #my-header {
  text-align: right;
}

If you prefer plain CSS, just rename the file; the parser still serves it. Remember to keep the file lean – MediaWiki bundles it with the other assets so bloat hurts page load times.

JavaScript – optional UI enhancements

The skin.js file runs on every page that uses your skin. Keep the footprint small; large frameworks will be stripped out by the resource loader.

// skin.js
mw.loader.using( [ 'mediawiki.util' ], function () {
    // Toggle the site notice on click
    $( '#siteNotice' ).on( 'click', function () {
        $( this ).fadeOut();
    } );
} );

Use mw.loader.using to declare dependencies; it prevents race conditions with other extensions.

Responsive design and viewport meta

Most modern wikis need to look decent on phones. Add the following meta tag in extension.json under ResourceModules:

{
    "ResourceModules": {
        "skins.mycoolskin": {
            "styles": "skin.less",
            "scripts": "skin.js",
            "targets": [ "desktop", "mobile" ],
            "position": "top",
            "dependencies": [],
            "messages": [],
            "dependencies": [],
            "skin": "MyCoolSkin",
            "version": "1.0.0"
        }
    }
}

The resource loader automatically injects <meta name="viewport" content="width=device-width,initial-scale=1"> for mobile skins. If you need a custom viewport, add it to skin.mustache inside a {{#head}}…{{/head}} block – but be aware that editing the HEAD is generally handled by hooks rather than the skin itself.

Right‑to‑left (RTL) considerations

Languages like Arabic or Hebrew flip the layout. MediaWiki sets the dir attribute on the html element. In your CSS you can target this attribute as shown earlier. It’s also wise to avoid hard‑coded float: left – use float: start instead, which respects the direction.

Testing your skin

There are a handful of manual steps you can take before publishing.

  1. Flush the cache: php maintenance/eval.php "wfResetOutputBuffers();" or simply purge a page.
  2. Visit Special:Version – your skin should be listed with the version number.
  3. Open a page in a private window, toggle the skin under user preferences, and verify the layout on both desktop and mobile.
  4. Use the browser’s dev tools to test dark mode (Ctrl+Shift+P → “Toggle dark mode”).
  5. Switch the interface language to an RTL language and glance at alignment.

If something looks off, the error is often a missing variable in the Mustache template or a stray CSS selector. Don’t be shy about consulting the official skin manual – it’s a gold mine of edge‑case tips.

Packaging for distribution

When you’re happy with the result, zip the MyCoolSkin directory and publish it on the MediaWiki Extension page. Include a short README.md that points people to the extension.json registration snippet. Remember to bump the version number with every change; the resource loader uses it for cache busting.

Advanced tweaks (optional)

For developers who need deeper integration, the following hooks can be useful:

  • BeforePageDisplay – inject additional <link> or <script> tags.
  • SkinTemplateNavigation – manipulate the navigation menus before they reach Mustache.
  • ResourceLoaderRegisterModules – register extra JS modules that aren’t part of the skin.

Here’s a quick example that adds a custom meta tag via a hook:

// in MyCoolSkinHooks.php
public static function onBeforePageDisplay( OutputPage $out, Skin $skin ) {
    $out->addMeta( 'custom-theme-color', '#336699' );
    return true;
}

Register the hook in your extension.json under Hooks:

{
    "Hooks": {
        "BeforePageDisplay": "MyCoolSkinHooks::onBeforePageDisplay"
    }
}

Wrapping up

Custom skin development is a blend of front‑end artistry and MediaWiki’s backend conventions. By following the steps above—setting up the folder, registering via extension.json, writing a Mustache template, styling with Less, and testing across devices—you’ll end up with a polished, maintainable theme that can sit alongside Vector and others without a hitch.

If you run into quirks, the community on Phabricator and the MediaWiki mailing lists is surprisingly helpful. The skin you build today could become tomorrow’s default for a niche wiki, or even a contribution back to the core ecosystem. Either way, you’ve earned a deeper look under the hood of one of the web’s most powerful collaborative platforms.

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