A merchant on Dawn 14.1 messaged me at 2:11 AM on a Friday in February 2026. She had added a “Featured Product” section to her homepage and the dropdown was empty. The schema set the type as product_picker instead of product. Two characters off. The fix took 20 seconds. The admin still rendered an empty dropdown until the cache cleared. The pattern below documents every supported type so the next merchant does not type the wrong one.
TL;DR: Shopify section schema supports 13 input types: text, textarea, number, range, checkbox, select, radio, image_picker, video_url, url, color, color_scheme, font_picker, html, richtext, page, blog, link_list, article, product, product_list, collection, collection_list, and metaobject. Each accepts an id, label, default, and type-specific options. The setting values live on section.settings.<id> in Liquid.
Why this matters for your store:
- A misnamed type breaks the editor UI silently. Theme Check catches some cases but not all of them.
- The 13 types map to 13 different merchant workflows. Picking the wrong type costs editors clicks every time they touch the section.
- Default values cascade to every new instance of the section. Setting good defaults reduces support tickets where a merchant adds a section and the page renders broken.
Text inputs: text, textarea, richtext, html
The four text-family types cover plain strings through formatted body copy. Each renders a different editor control.
{# sections/feature-banner.liquid - schema block #}
{% schema %}
{
"name": "Feature banner",
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading",
"default": "Built for the way you actually shop"
},
{
"type": "textarea",
"id": "subheading",
"label": "Subheading",
"default": "Three short lines that explain what the section is about."
},
{
"type": "richtext",
"id": "body",
"label": "Body copy",
"default": "<p>Body copy with <strong>inline emphasis</strong> the merchant can edit.</p>"
}
]
}
{% endschema %}
Use text for headlines and short labels. Use textarea for sentence-level copy. Use richtext when the merchant needs bold, italic, links, or lists inline. Use html only when the merchant pastes embed code (a YouTube iframe, a Calendly snippet). Liquid access pattern: {{ section.settings.heading }}. Rich text and html accept raw markup, so escape if a value will be reused in an attribute.
For broader Liquid mechanics, see my Shopify Liquid development guide and the 10 Shopify Custom Liquid section examples post for the full section-build pattern.
Number and range inputs
number accepts any integer. range adds min, max, and step constraints with a slider UI.
{% schema %}
{
"settings": [
{
"type": "number",
"id": "padding_top",
"label": "Padding top (px)",
"default": 48
},
{
"type": "range",
"id": "column_count",
"label": "Columns on desktop",
"min": 1,
"max": 5,
"step": 1,
"unit": "col",
"default": 3
}
]
}
{% endschema %}
Use range when the merchant should not type a value outside a known range. The slider also signals the constraint visually. Use number for unbounded integers (delay in milliseconds, item counts above the typical max).
Boolean and select inputs
{% schema %}
{
"settings": [
{
"type": "checkbox",
"id": "show_vendor",
"label": "Show vendor name on cards",
"default": false
},
{
"type": "select",
"id": "card_style",
"label": "Card style",
"options": [
{ "value": "minimal", "label": "Minimal" },
{ "value": "outlined", "label": "Outlined" },
{ "value": "filled", "label": "Filled" }
],
"default": "minimal"
},
{
"type": "radio",
"id": "alignment",
"label": "Text alignment",
"options": [
{ "value": "left", "label": "Left" },
{ "value": "center", "label": "Center" }
],
"default": "left"
}
]
}
{% endschema %}
checkbox returns true or false. select returns one of the listed value strings. radio is the same data shape as select but renders as radio buttons (better when there are 2-4 options the merchant should see at a glance).
In Liquid, conditional logic on these settings is straightforward:
{% if section.settings.show_vendor %}
<p class="vendor">{{ product.vendor }}</p>
{% endif %}
<div class="card card--{{ section.settings.card_style }} text-{{ section.settings.alignment }}">
…
</div>
Image, video, and file inputs
{% schema %}
{
"settings": [
{
"type": "image_picker",
"id": "hero_image",
"label": "Hero image"
},
{
"type": "video_url",
"id": "demo_video",
"label": "Demo video",
"accept": ["youtube", "vimeo"]
},
{
"type": "url",
"id": "cta_link",
"label": "Button URL"
}
]
}
{% endschema %}
image_picker returns an image object the merchant uploads or selects from the library. Pair with image_url and image_tag for the rendering pattern. For the full image_url reference, see my Shopify image_url filter complete reference.
video_url accepts a YouTube or Vimeo URL and returns a video_url object with id, type, and url. url accepts any string the merchant types as a link.
Color inputs
{% schema %}
{
"settings": [
{
"type": "color",
"id": "background",
"label": "Background color",
"default": "#FFFFFF"
},
{
"type": "color_scheme",
"id": "scheme",
"label": "Color scheme",
"default": "scheme-1"
}
]
}
{% endschema %}
color returns a single hex value. Use it for one-off color choices that do not belong to the theme palette.
color_scheme references the theme-level color scheme system (introduced in Dawn 7.0). Each color scheme defines background, text, button, and outline colors as a group. The merchant manages schemes once at the theme level and applies them per section. This is the right pattern for cohesive design.
Reference inputs (product, collection, page, blog, article, metaobject)
The reference family lets the merchant pick existing Shopify resources from the admin without typing IDs.
{% schema %}
{
"settings": [
{
"type": "product",
"id": "featured_product",
"label": "Featured product"
},
{
"type": "product_list",
"id": "featured_products",
"label": "Featured products",
"limit": 6
},
{
"type": "collection",
"id": "featured_collection",
"label": "Featured collection"
},
{
"type": "page",
"id": "support_page",
"label": "Support page"
},
{
"type": "blog",
"id": "featured_blog",
"label": "Featured blog"
},
{
"type": "article",
"id": "featured_article",
"label": "Featured article"
}
]
}
{% endschema %}
product returns the product object directly. Access .title, .handle, .featured_image without an additional all_products lookup:
{% assign featured = section.settings.featured_product %}
{% if featured != blank %}
<h2>{{ featured.title }}</h2>
<a href="{{ featured.url }}">View</a>
{% endif %}
product_list returns an array. limit caps how many products the merchant can pick. Use the same limit: and break patterns you would use on any Liquid loop. See my Shopify Liquid for loop break and continue reference.
Metaobject inputs
{% schema %}
{
"settings": [
{
"type": "metaobject",
"id": "selected_team_member",
"label": "Team member",
"metaobject_type": "team_member"
}
]
}
{% endschema %}
The metaobject type accepts a metaobject_type constraint that limits the picker to entries of one definition. Access in Liquid via section.settings.selected_team_member.fields.name, where the fields match the metaobject definition you set up in Settings > Custom data > Metaobjects.
For the broader decision on when to use metaobjects versus product metafields, see my Shopify metaobjects vs metafields post.
Blocks: schema within schema
Blocks are repeating sub-units inside a section. The schema declares block types; the merchant adds instances.
{# sections/feature-grid.liquid #}
{% schema %}
{
"name": "Feature grid",
"settings": [],
"max_blocks": 6,
"blocks": [
{
"type": "feature",
"name": "Feature",
"settings": [
{
"type": "image_picker",
"id": "icon",
"label": "Icon"
},
{
"type": "text",
"id": "heading",
"label": "Heading"
}
]
}
],
"presets": [
{
"name": "Feature grid",
"blocks": [
{ "type": "feature" },
{ "type": "feature" },
{ "type": "feature" }
]
}
]
}
{% endschema %}
{% for block in section.blocks %}
<div class="feature" {{ block.shopify_attributes }}>
{% if block.settings.icon != blank %}
{{ block.settings.icon | image_url: width: 96 | image_tag: alt: block.settings.heading }}
{% endif %}
<h3>{{ block.settings.heading }}</h3>
</div>
{% endfor %}
{{ block.shopify_attributes }} is mandatory inside the block render. It outputs the data attributes Shopify’s theme editor uses to highlight the selected block. Without it, the editor cannot scroll to a block when the merchant clicks it in the sidebar.
How to verify your schema
Three checks, three minutes.
-
Run
shopify theme check. The CLI walks every section’s{% schema %}block and flags malformed JSON, unknown types, and missing required keys. Fix everything Theme Check flags before opening the theme editor. -
Open the section in the theme editor. Add the section to a page and confirm every setting renders. A setting that does not render usually means a typo in
type:(theproduct_pickerversusproductfrom the hook) or a missing required option (selectwith nooptionsarray). -
Save default values and confirm they persist. A merchant adding the section for the first time should see the defaults in place. If a default is missing or wrong, the first impression of the section is broken UI.
The takeaway:
- The 13 supported types cover every common editor input. If you find yourself wanting a 14th, audit whether the merchant workflow can fit the existing set.
- Use
color_schemeovercolorwhen the theme palette already has the right scheme defined. Cohesion beats one-off hex picks. - Reference types (
product,collection,page) return the object directly. Skipall_products[handle]lookups when you have a picker available. - Block schema is the right pattern for repeatable content within one section. Section settings are for one-off choices that apply to the whole section.
- Run Theme Check on every PR. The schema check costs 2 seconds and catches typos that break the editor silently.