Skip to content

Loops

A loop repeats a block of markup once for every item in a collection. Write the opening tag with the collection to walk through and a name for the current item, then close the loop with {/each}. Between the tags you read the current item's fields with {alias.field}.

html
{each nav_links as=link}
  <a href="{link.url}">{link.label}</a>
{/each}

The runtime renders the block once per item and drops the item name when the loop ends, so the same alias can be reused in a later loop.

Reading fields from the current item

Use {alias.field} for any field the collection exposes, and add more segments to read a value nested inside a field. Only the current item is in scope, so {group.label} works inside a loop declared with as=group.

html
{each nav_groups as=group}
  <section class="menu-group">
    <h3>{group.label}</h3>
  </section>
{/each}

A field that resolves to nothing renders as an empty string, exactly like a token used outside a loop.

Closing the loop

{/each} closes the most recent loop. When blocks are long or nested you can add the collection name as a label, for example {/each nav_links}. The label is for readability, and it should match the collection named in the opening tag.

html
<ul>
  {each nav_links as=link limit=4}
    <li><a href="{link.url}">{link.label}</a></li>
  {/each nav_links}
</ul>

Limiting results with limit

Add limit=<n> to the opening tag to render at most n items, starting from the first. Use it to keep a preview short without changing the collection itself.

html
{each nav_links as=link limit=4}
  <li><a href="{link.url}">{link.label}</a></li>
{/each}

limit is a whole number greater than zero. Omitting it renders every item in the collection.

Nested loops

A loop can contain another loop. The inner loop runs in full for each item of the outer loop. Give each loop a different alias so both scopes stay readable.

html
<table>
  <thead>
    <tr><th>Group</th><th>Link</th></tr>
  </thead>
  <tbody>
    {each nav_groups as=group}
      {each group.links as=link limit=3}
        <tr>
          <td>{group.label}</td>
          <td><a href="{link.url}">{link.label}</a></td>
        </tr>
      {/each group.links}
    {/each}
  </tbody>
</table>

Form field groups

Login, signup, and settings forms expose their fields as groups. The for=<name> part selects which form definition to use, and each group exposes {group.columns} together with a fields collection.

html
{each form.fieldGroups for=settings as=group}
  <div class="grid grid-cols-{group.columns}">
    {each group.fields as=field}
      <label for="{field.id}">{field.label}</label>
      <input id="{field.id}" name="{field.id}" placeholder="{field.placeholder}" type="{field.type}">
    {/each group.fields}
  </div>
{/each form.fieldGroups}

A select field carries its own options collection. Loop over it inside the field loop to render each choice.

html
{each field.options as=opt}
  <option value="{opt.value}">{opt.label}</option>
{/each field.options}

Handling an empty collection

An {each} over an empty collection renders nothing, which can leave an empty section behind. Wrap the loop in {if <collection>} to show the markup only when there is at least one item, and use {else} for an empty state.

html
{if nav_links}
  <nav aria-label="Main navigation">
    <ul>
      {each nav_links as=link}
        <li><a href="{link.url}">{link.label}</a></li>
      {/each}
    </ul>
  </nav>
{else}
  <p>No navigation links yet.</p>
{/if}

The same guard works for any collection, such as a list of catalog templates.

html
{if templates.items}
  <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
    {each templates.items as=template limit=6}
      <article>
        <h3>{template.name}</h3>
        <p>{template.description}</p>
      </article>
    {/each templates.items}
  </div>
{/if}

Collections you can loop over

CollectionWhat it yieldsExample opening tag
nav_links, nav_links_footer, nav_groups, nav_groups_footerSite navigation links and their groups{each nav_links as=link}
templates.items, template.tagsCatalog templates and their tags{each templates.items as=template}
form.fieldGroups, group.fields, field.optionsForm groups, fields, and select options{each form.fieldGroups for=login as=group}

Collections that come from an extension are documented with that extension. See Commerce extension templating, Portfolio extension templating, and Blog extension templating.

Syntax at a glance

SyntaxMeaning
{each <collection> as=<alias>}Starts a loop over the collection and names the current item
{alias.field}Reads a field from the current item
{/each}Closes the most recent loop
{/each <collection>}Closes the loop with a readability label
limit=<n>Renders at most n items
for=<form>Selects the form definition for form.fieldGroups
{if <collection>}Runs the block only when the collection has items