Templates

Your blocks are just HTML

How Templating Works

If you understand these three things, you understand 90% of templating.

  1. {{variable}} prints a field

  2. {{#if}} hides or shows markup

  3. {{#each}} repeats markup

That's it. You write normal HTML. Handlebars decides whether it shows up and how many times.

<div>
  <!-- Print a field -->
  <h2>{{headline}}</h2>
  
  <!-- Show markup conditionally -->
  {{#if description}}
    <p>{{description}}</p>
  {{/if}}
  
  <!-- Repeat markup for each item -->
  {{#each items}}
    <div>{{name}}</div>
  {{/each}}
</div>

Displaying Data

Field values print into your markup. Variables, group fields, and rich text each output a little differently.

Variables

Field names become variables. A field named "Headline" becomes {{headline}}. Automatic kebab-case conversion. See Fields for details.

<h1>{{headline}}</h1>
<p>{{description}}</p>
<img src="{{image}}" alt="{{image.alt}}">
<a href="{{cta-url}}">{{cta-text}}</a>

Group Fields

Use dot notation for grouped fields.

{{settings.background-color}}
{{author.name}}
{{metadata.publish-date}}

WYSIWYG Fields

WYSIWYG fields return HTML. Use triple braces.

<!-- Wrong - HTML will be escaped -->
<div class="prose">{{content}}</div>

<!-- Right - HTML renders properly -->
<div class="prose">{{{content}}}</div>

Security note. Triple braces render HTML without escaping. Only use them for WYSIWYG fields where you trust the content source.

Conditionals

If/Else

{{#if description}}
  <p>{{description}}</p>
{{/if}}

{{#if featured-image}}
  <img src="{{featured-image}}" alt="{{headline}}">
{{else}}
  <div>No image</div>
{{/if}}

Switch

{{#switch plan}}
  {{#case "free"}}
    <span>Free</span>
  {{/case}}
  {{#case "pro" "team"}}
    <span>Paid</span>
  {{/case}}
  {{#otherwise}}
    <span>Unknown plan</span>
  {{/otherwise}}
{{/switch}}

Is Equal

There's also eq, the subexpression form, for use inside {{#if}} or another helper: {{#if (eq status "published")}}…{{/if}}.

{{#isEqual status "published"}}
  <span>Published</span>
{{else}}
  <span>Draft</span>
{{/isEqual}}

<!-- Number fields store strings, so compare against a quoted value -->
{{#isEqual count "0"}}
  <p>No items</p>
{{else}}
  <p>{{count}} items</p>
{{/isEqual}}

Displaying Lists

The items field generates arrays (lists) of objects with properties. Data stores also generate this structure.

Loop

Repeat markup for each item in a list.

<!-- Headline and description are unique for each item -->
{{#each items}}
  <div>
    <h3>{{headline}}</h3>
    <p>{{description}}</p>
  </div>
{{/each}}

<!-- Access the loop index -->
{{#each items}}
  <div class="item-{{@index}}">
    <span>#{{@index}}</span>
  </div>
{{/each}}

First and Last

Only the first or last item.

{{#with (first items)}}
  <h2>Just the first item</h2>
{{/with}}

{{#with (last items)}}
  <h2>Just the last item</h2>
{{/with}}

Length

Get the length of the list of items.

<p>Showing {{length items}} items</p>

{{#if (length items)}}
  <h2>We have {{length items}} items</h2>
{{/if}}

{{#isEqual (length items) 2}}
  <h2>We have two items</h2>
{{/isEqual}}

Debugging

Not sure what data you have? Output it. The JSON helper shows you the exact structure of your data.

<!-- See everything available -->
<pre>{{json this}}</pre>

<!-- Check a specific field -->
<pre>{{json field-name}}</pre>

<!-- Debug an Items field -->
<pre>{{json features}}</pre>

Preview-only. {{json}} works in the editor preview, but there's no server-side json helper. Leave it in and the deployed block fails to compile, showing a "Template compilation error" in its place. Pull it out before you publish.

Layouts

Layouts wrap pages with shared structure: headers, footers, and navigation.

Every page has one layout. Layouts have two sections: header blocks (top) and footer blocks (bottom). Page content renders between them. Editing the layout updates every page that uses it.

The layouts editor has a CSS field so you can add styles that load with a specific layout. Use this to modify the structure. You can adjust where the header, footer and main content appear. It doesn't need to be in the traditional stacked arrangement.

<style>
body {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-height: 100%;
}

/* The content between the header and footer */
main {
  flex:1;
}

 /* Wraps for header and footer blocks */
div[data-section="header"] {}
div[data-section="footer"] {}
</style>

Layouts have a custom CSS section that will apply only to pages using that layout.

Quick Fixes

<!-- Variable doesn't match field name -->
{{heroHeadline}}    <!-- ✗ Incorrect casing -->
{{hero-headline}}   <!-- ✓ Field names are kebab-case -->

<!-- WYSIWYG needs triple braces -->
{{content}}         <!-- ✗ Shows escaped HTML -->
{{{content}}}       <!-- ✓ Renders HTML -->

<!-- Partial name must match exactly -->
{{> button}}        <!-- ✗ Case matters -->
{{> Button}}        <!-- ✓ Matches partial name -->

<!-- Loop not iterating? Check if data exists -->
{{#if items}}
  <p>Found {{length items}} items</p>
{{else}}
  <p>No items - check field name and data</p>
{{/if}}

<!-- Missing closing tag -->
{{#if condition}}
  Content

<!-- Incorrect closing tag -->
{{#each items}}
  {{headline}}
{{/if}}
Thanks — your message has been sent.