Using Mustache templates in MediaWiki skins to define page structure.
MediaWiki uses Mustache for skin templating via the SkinMustache base class. Templates receive a structured data object from getTemplateData() and render it into HTML. The template engine is implemented using LightnCandy, a PHP Mustache/Handlebars compiler.
SkinMustache (in MediaWiki\Skin\SkinMustache) extends SkinTemplate and provides Mustache rendering. It uses TemplateParser to compile and execute .mustache files.
includes/Skin/SkinMustache.php
class SkinMustache extends SkinTemplate { protected function getTemplateParser(): TemplateParser { if ( $this->templateParser === null ) { $this->templateParser = new TemplateParser( $this->options['templateDirectory'] ); // Enable recursive partials for table of contents rendering $this->templateParser->enableRecursivePartials( true ); } return $this->templateParser; } public function generateHTML(): string { $this->setupTemplateContext(); $tp = $this->getTemplateParser(); $template = $this->options['template'] ?? 'skin'; $data = $this->getTemplateData(); return $tp->processTemplate( $template, $data ); }}
The templateDirectory option in skin.json points to the directory containing .mustache files. The template option (default: 'skin') specifies the root template name, which resolves to skin.mustache in that directory.
MediaWiki uses standard Mustache with LightnCandy’s FLAG_MUSTACHELOOKUP flag enabled.
Variables
Sections
Inverted sections
Partials
Use {{variable}} for HTML-escaped output and {{{variable}}} for raw (unescaped) HTML:
<!-- Escaped: safe for user-supplied text --><title>{{html-title}}</title><!-- Unescaped: for pre-built HTML strings from MediaWiki --><div class="mw-body-content">{{{html-body-content}}}</div>
All html-* keys from getTemplateData() contain pre-sanitised HTML and should be rendered with triple braces {{{ }}}. Plain text values should use double braces {{ }}.
{{#key}}...{{/key}} renders the block if the value is truthy. For arrays, it iterates:
SkinMustache::getTemplateData() assembles data from Skin::getTemplateData() (via SkinTemplate) and adds Mustache-specific keys. Data keys follow a consistent naming convention:
Override getTemplateData() in your skin class to provide additional keys:
public function getTemplateData(): array { $data = parent::getTemplateData(); // Add portlet data for navigation menus // $data['data-portlets'] is assembled by SkinTemplate::getTemplateData() // and contains keys like 'data-views', 'data-actions', 'data-user-menu', etc. // Add a custom skin-specific flag $data['is-sidebar-collapsed'] = $this->isSidebarCollapsed(); // Expose a config value as a string to templates $data['html-custom-banner'] = $this->getConfig()->get( 'MyThemeBanner' ) ? $this->msg( 'mytheme-banner' )->parse() : null; return $data;}
Partials let you split a skin template into reusable pieces. Place partial files in the same templateDirectory as your root skin.mustache:
skins/MyTheme/templates/├── skin.mustache # root template├── navigation.mustache # partial for the nav bar├── sidebar.mustache # partial for the sidebar└── footer.mustache # partial for the footer
Partials inherit the full data context from the parent template. Recursive partials (used for the table of contents) are enabled by default in SkinMustache:
Do not disable enableRecursivePartials in your skin’s getTemplateParser() override unless you are certain your skin never renders a table of contents. Disabling it will break ToC rendering.
All html-* values in the template data contain HTML that has already been sanitised by MediaWiki’s HTML sanitiser (MediaWiki\Parser\Sanitizer) or constructed by trusted code using Html::element() / Html::rawElement(). These values must be rendered with triple braces {{{ }}} to prevent double-escaping.
Triple braces: raw HTML
Use {{{html-body-content}}} for pre-sanitised HTML strings returned by MediaWiki. Never use triple braces with user-supplied text.
Double braces: escaped
Use {{msg-search}} for plain text values (message strings, URLs, class names) that should be HTML-escaped by the template engine.
The data key naming convention enforces this: any key prefixed html- is already safe HTML; any key without that prefix is plain text that should go through Mustache’s escaping.
<!-- Correct: html- prefix means pre-sanitised HTML -->{{{html-body-content}}}{{{html-title-heading}}}<!-- Correct: plain text, double-brace escaped --><html lang="{{html-lang}}" dir="{{html-dir}}"><meta name="description" content="{{msg-tagline}}">
Write a PHPUnit test that instantiates your skin with a mock context and calls getTemplateData(). Assert that required keys are present and have the expected types.
Load the wiki in a browser with ?useskin=mytheme appended to any URL to preview your skin without changing the site default. Combine with ?safemode=1 to disable all user scripts and gadgets.
During development, set $wgMainCacheType = CACHE_NONE; and $wgMessageCacheType = CACHE_NONE; in LocalSettings.php to prevent stale compiled templates from being served from the object cache.