Bound Settings#
Most settings store a literal value. A BOUND setting stores a reference — a menu handle, an entity id, a list of ids, or a ctx path — and the host swaps it for live data before your template runs. Your markup always reads resolved objects, never raw ids. One law covers every binding: it works IDENTICALLY on section settings and on block settings, at any nesting depth.
Menus#
A "menu"-typed setting stores the picked menu's HANDLE. At render the host loads that one menu and injects the resolved tree beside it as settings.
{% if settings.menu_resolved.items.size > 0 %}
<nav aria-label="{{ settings.menu_resolved.name }}">
{% for item in settings.menu_resolved.items %}
<a href="{{ item.url }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}>{{ item.title }}</a>
{% for child in item.children %} … {% endfor %}
{% endfor %}
</nav>
{% endif %}
Resolved tree field |
Meaning |
|---|---|
name |
The menu's display name. |
items |
Ordered top-level items. |
items[].title / url / open_in_new_tab |
Link fields, host-resolved (a deleted target page drops the item's stale URL for its live one — links never 404 from staleness). |
items[].children |
Nested items, recursively (three levels). |
Menu settings on BLOCKS resolve the same way: a footer's menu block can carry its own "menu" setting, read b.settings.menu_resolved, and fall back to the section-level pick — multiple menu blocks, each showing a different menu.
Resource pickers#
A picker setting stores the picked entity's STABLE id; at render the host resolves it IN PLACE — {{ settings.product }} IS the product card object. A deleted or unresolvable entity becomes nil: guard with {% if settings.product %} and render your placeholder. Picker types: vendor, category, brand, product, collection (integer-id family), blog_post, blog_category, page (uuid family), and policy (picked by fixed policy type).
{% if settings.product %}
{% assign p = settings.product %}
<a href="{{ p.url }}">
{% if p.featured_image %}<img src="{{ p.featured_image.url | image_url: width: 480 }}" alt="{{ p.featured_image.alt_text | default: p.title }}">{% endif %}
<span>{{ p.title }}</span>
<span>{% if p.price_range.has_range %}{{ 'products.from' | t }} {{ p.price_range.min | money }}{% elsif p.price %}{{ p.price.current | money }}{% endif %}</span>
</a>
{% else %}
{{ 'product-1' | placeholder_svg }}
{% endif %}
List pickers#
product_list and collection_list store an ORDERED list of ids — the merchant picks up to "max" (default 12), reorders freely, and the editor renders chips with add/remove/reorder. At render each id resolves to the same card object the single picker yields; deleted entries DROP, so the list you iterate is always dense and ordered. Saving deduplicates and caps at max.
{% for p in settings.products %}
<article class="card"> … </article>
{% endfor %}
{% if settings.products.size == 0 %} …placeholder cards… {% endif %}
{% schema %}
{ "settings": [
{ "id": "products", "type": "product_list", "max": 8,
"editor": {"label": "products", "group": "content"} }
] }
{% endschema %}
Dynamic sources#
A text or textarea setting may store the sentinel "@dynamic:
// stored setting value (set via the editor's connect-dynamic-source flow)
"title": "@dynamic:product.title"
// renders as the live product title — the template just writes
{{ settings.title }}