A Handlebars formatter based on the Glimmer parser
This Handlebars formatter uses Prettier's glimmer parser, the template parser from Ember's Glimmer rendering engine. Glimmer templates are a superset of Handlebars: they add angle-bracket components (<UserCard @user={{this.user}} />), named arguments with @, and element modifiers such as {{on "click" this.save}}. Classic Handlebars templates used with Handlebars.js or Express view engines are parsed by the same grammar, as long as they only use standard mustache and block syntax. Paste the contents of an .hbs or .handlebars file directly.
Example
<ul class="todo-list">{{#each @todos key="id" as |todo|}}<li class="{{if todo.done 'done'}}"><Input @type="checkbox" @checked={{todo.done}} {{on "change" (fn this.toggle todo)}} />{{todo.title}}</li>{{else}}<li>Nothing to do</li>{{/each}}</ul>
<ul class="todo-list">{{#each @todos key="id" as |todo|}}<li
class="{{if todo.done 'done'}}"
><Input
@type="checkbox"
@checked={{todo.done}}
{{on "change" (fn this.toggle todo)}}
/>{{todo.title}}</li>{{else}}<li>Nothing to do</li>{{/each}}</ul>
Because the input had no whitespace between tags and mustaches, the formatter keeps them touching and only wraps the long element's attributes. Whitespace in Handlebars output is visible to users, so Prettier will not invent it. Write your source with line breaks between block helpers and elements, and the formatter indents each level properly.
What gets formatted
- Block helpers:
{{#if}},{{else if}},{{#each}}with block params,{{#let}},{{#unless}}. - Components and their
@arguments, one per line once they exceed the line width. - Sub-expressions like
(fn this.toggle todo)and hash arguments likekey="id". - Comments in both forms,
{{! short }}and{{!-- long --}}.
Matching your project's Prettier settings
Prettier picks the glimmer parser automatically for .hbs and .handlebars files, so an Ember or Handlebars project that already runs npx prettier --write "app/**/*.hbs" uses the same printer as this page. To get identical output, set Indent and Line width here to the tabWidth and printWidth from your .prettierrc; otherwise the next commit hook will reformat what you pasted.
Line width decides where wrapping starts. An element whose tag and attributes fit on one line stays on one line; once it is longer, each attribute, @argument and modifier moves to its own line, as in the example above. Formatting is only about layout, so pair it with a linter such as ember-template-lint if you also want rules about accessibility or deprecated syntax.
Handlebars and Glimmer syntax at a glance
| Syntax | Meaning |
|---|---|
{{title}} | Output a value, HTML-escaped |
{{{html}}} | Output raw, unescaped HTML |
{{this.count}} / {{@count}} | Glimmer: component property vs. argument passed in |
{{~#if a~}} | Tilde strips whitespace on that side of the mustache |
{{!-- note --}} | Comment that may contain }} |
The formatter keeps tildes and comment styles exactly as written, so whitespace control in your output does not change.
Common parse errors
- Unclosed blocks: every
{{#each}}needs a matching{{/each}}. - Partials (
{{> header}}) are Handlebars.js features that Glimmer does not support, so templates using them fail to parse. Format those files with the HTML Formatter instead. - Triple-stash
{{{html}}}is supported, but be careful: it outputs unescaped HTML.
Ember component classes are JavaScript or TypeScript; format them with JavaScript Pretty Print. Templates are processed locally in your browser.