Blocks
Add repeatable, sortable content items inside sections.
Blocks are repeatable, sortable content items inside a section. A slideshow section contains slide blocks. A testimonials section contains individual testimonial blocks. The visual editor lets merchants add, remove, and rearrange these blocks without writing any template code.
Defining blocks
Add a blocks array to your section schema. Each entry describes one type of block:
{% schema %}
{
"name": "Slideshow",
"blocks": [
{
"type": "slide",
"name": "Slide",
"settings": [
{ "type": "image_picker", "id": "image", "label": "Image" },
{ "type": "text", "id": "heading", "label": "Heading" },
{ "type": "text", "id": "subheading", "label": "Subheading" },
{ "type": "url", "id": "link", "label": "Link" }
]
}
]
}
{% endschema %}The type is a unique identifier you choose for this block variety. The name is the human-readable label that the merchant sees when adding blocks in the editor sidebar. The settings array follows the exact same structure as section-level settings.
Rendering blocks
Loop over section.blocks in your template. Each block has an id, type, and settings object:
<div class="slideshow">
{% for block in section.blocks %}
<div class="slide" data-block-id="{{ block.id }}">
{% if block.settings.image %}
{{ block.settings.image | image_tag }}
{% endif %}
<h2>{{ block.settings.heading }}</h2>
<p>{{ block.settings.subheading }}</p>
</div>
{% endfor %}
</div>Always include the data-block-id attribute on each block's outermost element. The visual editor relies on this attribute to highlight the corresponding block when the merchant selects it in the sidebar.
Multiple block types
A section can have more than one type of block. Use block.type to tell them apart:
{% schema %}
{
"name": "Content Grid",
"blocks": [
{
"type": "text_block",
"name": "Text",
"settings": [
{ "type": "richtext", "id": "content", "label": "Content" }
]
},
{
"type": "image_block",
"name": "Image",
"settings": [
{ "type": "image_picker", "id": "image", "label": "Image" },
{ "type": "text", "id": "caption", "label": "Caption" }
]
},
{
"type": "video_block",
"name": "Video",
"settings": [
{ "type": "video_url", "id": "url", "label": "Video URL" }
]
}
]
}
{% endschema %}In the template, check the type for each block:
{% for block in section.blocks %}
<div class="grid-item" data-block-id="{{ block.id }}">
{% case block.type %}
{% when 'text_block' %}
<div class="text-content">{{ block.settings.content }}</div>
{% when 'image_block' %}
{{ block.settings.image | image_tag }}
{% if block.settings.caption != blank %}
<p class="caption">{{ block.settings.caption }}</p>
{% endif %}
{% when 'video_block' %}
<iframe src="{{ block.settings.url }}" allowfullscreen></iframe>
{% endcase %}
</div>
{% endfor %}Limiting blocks
Set max_blocks at the top level of your schema to cap how many blocks the merchant can add:
{% schema %}
{
"name": "Testimonials",
"max_blocks": 6,
"blocks": [
{
"type": "testimonial",
"name": "Testimonial",
"settings": [
{ "type": "text", "id": "quote", "label": "Quote" },
{ "type": "text", "id": "author", "label": "Author" }
]
}
]
}
{% endschema %}Empty state
Always handle the case where no blocks have been added yet:
{% if section.blocks.size == 0 %}
<p>No slides added yet. Open the editor to add one.</p>
{% else %}
{% for block in section.blocks %}
{% render 'slide' with block %}
{% endfor %}
{% endif %}Block properties
Each block in the loop gives you these values:
| Property | Description |
|---|---|
block.id | A unique ID for DOM targeting |
block.type | The type string from the schema |
block.settings | The values the merchant set for this block |
Tips
- Keep block schemas small. A block with more than five or six settings becomes hard for merchants to manage.
- Give blocks clear names. The merchant sees the name in the editor sidebar when adding new blocks.
- Use
case/whenfor multiple block types. It is cleaner than a chain ofif/elsifchecks. - Include default values for settings that should never be empty. This makes the section look good before the merchant makes any changes.