All posts
7 min read

How do I loop through blocks in a Shopify section?

Loop over the blocks with {% for block in section.blocks %}, branch on block.type to decide what each one draws, and read its fields from block.settings. Put {{ block.shopify_attributes }} on the outer element of every block, so the theme editor can find it when a merchant clicks it in the sidebar.

That is the whole pattern. The rest of this page covers what the loop actually contains, which we checked by uploading a test section to a Shopify development store on 8 October 2026 with Shopify CLI 3.93.1, and the three errors you get when the blocks in a template do not match the schema.

What does the basic block loop look like?

This is the shape Shopify's own section blocks documentation uses, with the editor attribute added:

{%- for block in section.blocks -%}
  <div class="faq-item" {{ block.shopify_attributes }}>
    {%- case block.type -%}
      {%- when 'question' -%}
        <h3>{{ block.settings.question }}</h3>
        {{ block.settings.answer }}
      {%- when 'note' -%}
        <p class="faq-note">{{ block.settings.text }}</p>
    {%- endcase -%}
  </div>
{%- endfor -%}

The types you write after when must match the type values in the section's {% schema %} blocks array exactly. The schema decides which blocks a merchant can add; the loop decides what gets drawn. A section can declare blocks perfectly and still show nothing if the loop is missing, which is a common reason an added block "does nothing".

If the section only has one block type, you can skip the case. In the 8 paid themes we generated and kept for testing, 70 sections loop over blocks, and 48 of them have a single block type and no type check at all. Of the other 22, 21 use {% if block.type == '...' %} and one uses case. Both work the same; case just reads better once there are three or more types.

What is actually inside section.blocks?

Our test section declared two block types, item and note, and its template added four blocks: Alpha (item), Bravo (note), Charlie (item, hidden in the editor with the eye icon) and Delta (item). Here is what Shopify rendered.

LiquidWhat it returned
section.blocks.size3, not 4. The hidden block is not in the array at all
forloop.length3
forloop.index1, 2, 3 (Delta is 3, because Charlie was skipped)
block.idThe block's key from the template JSON (a, b, d in our file)
block.typeitem, note, item
block.shopify_attributesEmpty on the live storefront
section.blocks.first.settings.textAlpha

Two of those are worth knowing before you write any logic. First, hidden blocks are removed before your code runs, so a counter or a "first item" check never sees them. Second, block.id is whatever key the template happens to use, and Shopify's block object documentation says the ID is generated by Shopify and subject to change. Use it for unique HTML ids within one page, never as a stable value in CSS or JavaScript.

The empty shopify_attributes is expected. Shopify's documentation says no value is returned outside the theme editor, so it costs nothing on the live store.

How do I show only some of the blocks?

All three of these worked in the same test, against the three visible blocks Alpha, Bravo and Delta:

You wantLiquidRendered
Only the first two{% for block in section.blocks limit: 2 %}Alpha, Bravo
Everything after the first{% for block in section.blocks offset: 1 %}Bravo, Delta
One type only{% assign notes = section.blocks | where: 'type', 'note' %}1 block: Bravo
Just the first blocksection.blocks.firstAlpha
A fallback when there are none{% if section.blocks.size == 0 %}Your empty state

The offset: 1 pattern is the usual way to build a "featured first item, grid for the rest" layout: render section.blocks.first large, then loop with offset: 1 for the remainder. The where filter is the cleaner way to pull, say, every slide into a carousel and every caption somewhere else, without two loops full of if checks.

Because hidden blocks are already gone, limit: 2 means the first two visible blocks. A merchant who hides the first block will see the third one move up, which is usually what they expect.

Why does block.shopify_attributes matter?

Shopify's theme editor documentation says sections get their editor attributes automatically, but blocks need them added by hand with block.shopify_attributes. The editor looks for those data attributes on the block's outer element to know which part of the preview belongs to which block in the sidebar. Without them, the editor cannot match a block in the sidebar to its place in the preview, and the shopify:block:select and shopify:block:deselect JavaScript events have nothing to target, which breaks things like a slideshow jumping to the slide being edited.

Put it on one element per block, the outermost one, inside the loop. It is an easy line to forget. Of the 70 block loops in our generated themes, 69 include it; the one that does not is an announcement bar whose rotating messages are blocks.

What errors does a block loop run into on upload?

The loop itself rarely errors. The template JSON that feeds it does. We pushed three broken versions of our test template, one at a time, against a schema with "max_blocks": 6 and "limit": 2 on the note type. Shopify uploaded the theme but rejected templates/index.json each time, with these messages:

What the template didShopify's messageFix
Added 7 blocks to a section capped at 6Block count exceeds maximum of 6 for section 'blt'.Remove blocks, or raise max_blocks (50 is the ceiling)
Used a block type the schema does not declareInvalid value for type in block 'z'. Type must be defined in schema.Add the type to the schema's blocks array, or fix the spelling
Used the note type 3 times with "limit": 2Block type 'note' cannot be used more than 2 times.Remove one, or raise that type's limit

All three come from the same mismatch: a template written for one version of a section, uploaded with a schema that has since changed. That happens most when you rename a block type or lower a cap in the schema after the store already uses the section. Search the templates folder for the old type name before you upload. Our guide to why a Shopify theme ZIP will not upload covers how one rejected file can hide others, and a blank default inside a block's settings produces its own error, explained in what "default can't be blank" means.

Is section.blocks the same as content_for 'blocks'?

No. They are two different kinds of block, and a section uses one or the other.

Section blocksTheme blocks
Defined inThe section's own {% schema %}Their own files in the theme's blocks folder
How the section opts inLists each block type in its blocks arrayAdds { "type": "@theme" } to its blocks array
How they are rendered{% for block in section.blocks %}{% content_for 'blocks' %}
Reusable in other sectionsNoYes

Shopify's theme blocks documentation says a section can define blocks locally or accept theme blocks, but not both at once. If the theme you are editing has a blocks folder and its sections contain content_for 'blocks', writing a section.blocks loop in them is the wrong tool. App blocks are a third case: they are declared as { "type": "@app" } and drawn inside the normal loop with {% render block %}, covered in why you cannot add an app's block to your theme.

For the plain language version of what a block is and why most sections do not offer an Add block button, see the difference between a section and a block in Shopify.

Where these numbers come from

The rendered values and the three error messages come from one test section pushed as an unpublished theme to a Shopify development store on 8 October 2026 with Shopify CLI 3.93.1, then deleted. The loop counts come from parsing every section file in 8 complete paid themes generated by Themr and kept for testing: 203 section files, 70 of which declare blocks, and all 70 of those loop over them. What shopify_attributes is for, the 50 block ceiling and the theme block rules are Shopify's documented behavior, not our measurements. If you would rather start from a theme where every block loop is already wired up, the free themes in our gallery are complete and upload as they are.

Frequently asked questions

How do I loop through blocks in a Shopify section?

Use {% for block in section.blocks %} in the section file, branch on block.type with case or if, and read each block's fields from block.settings. Add {{ block.shopify_attributes }} to the outer element of each block so the theme editor can select it.

Do hidden blocks show up in section.blocks?

No. In our 8 October 2026 test, a section with four blocks, one hidden in the theme editor, returned section.blocks.size of 3, and forloop.length and forloop.index skipped the hidden block entirely.

How do I show only the first few blocks of a Shopify section?

Add limit to the loop: {% for block in section.blocks limit: 2 %} renders the first two visible blocks. Use offset: 1 to skip the first block, and section.blocks.first to render just the first one.

Can I filter section.blocks by type?

Yes. {% assign notes = section.blocks | where: 'type', 'note' %} returns only the blocks of that type, and it worked on a real store in our test. Loop over the result like any other array.

What does block.shopify_attributes do?

It outputs the data attributes the theme editor uses to match a block in the sidebar to its element in the preview, so selecting a block highlights it and fires shopify:block:select. Shopify returns nothing for it on the live storefront.

What does "Block count exceeds maximum" mean in Shopify?

A template has more blocks in a section than that section's max_blocks allows. Remove blocks from the template or raise max_blocks in the schema. Shopify's ceiling is 50 blocks per section.

Is section.blocks the same as content_for 'blocks'?

No. section.blocks loops over blocks defined in the section's own schema. content_for 'blocks' renders theme blocks, which live in the theme's blocks folder and are accepted with the @theme type. Shopify says a section can use one kind or the other, not both.

Generate a theme that looks like your brand

A complete Shopify 2.0 theme with conversion features built in, ready in minutes. No credit card required.

Generate my theme free

No Shopify store yet? Start one here, then bring the theme. Themr may earn a commission if you start a paid plan; it does not change what you pay.