If you’ve worked in a large PHP codebase like Moodle, you’ve bumped into files ending in .mustache and wondered what those double curly braces are doing. 🐘 The short version: Mustache is a logic-less templating language, and the syntax you learn once works in about 40 other languages too. It’s a specification, not just a library.
The whole language fits in a handful of tag types. Here they are, with the behavior that trips people up:
1. Escaped values — {{ name }} outputs a value with HTML escaping applied, so an angle bracket comes out as a harmless entity. This is the safe default for any text.
2. Raw values — {{{ icon }}} with triple braces emits the value as-is, no escaping. Use it only for trusted HTML you deliberately built (an SVG string, say). Everything else stays double-braced.
3. Sections — {{#thing}} … {{/thing}} renders the inner block when thing is truthy. If thing is a list, the very same syntax loops instead. Context decides.
4. Inverted sections — {{^thing}} … {{/thing}} is the only “else” Mustache gives you: it renders when the value is falsy. There is no {{else}} keyword. 💡
Put a section and an inverted section on the same variable and you get the classic if/else. Here’s a real example: an action button that’s a working link when a feature is on, and an inert, greyed-out button with a tooltip when it’s off.
1 2 3 4 5 6 | {{#disabled}} <a class="btn disabled" href="#" aria-disabled="true" tabindex="-1" title="{{ disabledhint }}">{{ label }}</a> {{/disabled}} {{^disabled}} <a class="btn" href="{{ url }}">{{ label }}</a> {{/disabled}} |
Notice what’s missing: there are no operators. You can’t write a logical-and or an equality check inside the braces. That’s the entire philosophy — the template decides which pre-computed pieces to show, but the deciding is done in your code. So the disabled boolean and the disabledhint string are both computed in PHP and handed over ready-made.
Speaking of PHP: rendering is refreshingly boring.
1 2 3 4 5 6 7 8 9 10 11 12 | <?php $engine = new Mustache_Engine([ 'loader' => new Mustache_Loader_FilesystemLoader(__DIR__ . '/templates'), ]); $context = [ 'disabled' => true, 'label' => 'Edit Profile', 'disabledhint' => 'Feature flag must be enabled', ]; echo $engine->render('account_card', $context); |
The context is just an array (or object). PHP truthiness maps straight onto sections: false, null, an empty string, 0, and the empty array are all falsy and skip the section (and trigger the inverted one); a non-empty array becomes a loop. Closures passed as values act as “lambdas” — the one sanctioned place a sliver of logic sneaks back in.
Frameworks usually wrap this. In Moodle, you never touch Mustache_Engine directly — you call $OUTPUT->render_from_template(‘local_cert/pv/account_card’, $context), and the framework resolves the component path, injects helpers like {{#str}} key, component {{/str}} for translations, and supports template inheritance. The neat part: the same template file can also be rendered client-side by Moodle’s JavaScript layer, because there’s a JavaScript implementation of Mustache that produces identical output.
That cross-language portability is the real reason to like it. Learn the five tag types once, and you can read a template in Ruby, Go, JavaScript, or PHP without relearning a thing. The host Application Programming Interface (API) differs; the braces never do. 🎉