Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.uselayers.com/llms.txt

Use this file to discover all available pages before exploring further.

Overview

This is the SDK equivalent of Rendering blocks in Liquid. The shape of the section is the same — one Liquid section, one merchandiser-facing block-picker setting — but client.blocks() does the HTTP call, dedupes against in-flight requests, caches results in localStorage, and exposes the products on a reactive signal. You do not need to handle cart context, click feedback, or cross-block product deduplication. The Layers theme app extension handles those for every Blocks request on the storefront. Use this version when you’ve already installed @commerce-blocks/sdk in your theme. For a no-dependencies version, see the Liquid + Fetch guide.

Prerequisites

  • The Layers Shopify app installed and the theme app extension enabled in Online Store → Themes → Customize → App embeds.
  • The SDK installed and a client created in your theme — see SDK installation and the setup snippet for generating sorts/facets from metaobjects.

The section

sections/layers-recommendations.liquid is the same Liquid scaffold as the Fetch version — a section with a block-picker setting and a single placeholder element carrying the selected block handle.
{% assign selected_block_id = section.settings.block_id %}

{% if selected_block_id != blank %}
  <section
    class="layers-recommendations"
    data-block-id="{{ selected_block_id }}"
    data-anchor-id="{% if template == 'product' %}{{ product.id }}{% elsif template == 'collection' %}{{ collection.handle }}{% endif %}"
  >
    {% if section.settings.heading != blank %}
      <h2>{{ section.settings.heading }}</h2>
    {% endif %}
    <ol class="layers-recommendations__products" data-loading="true"></ol>
  </section>
{% endif %}

{% schema %}
{
  "name": "Layers Recommendations",
  "settings": [
    {
      "type": "text",
      "id": "heading",
      "label": "Heading",
      "default": "You may also like"
    },
    {
      "type": "select",
      "id": "block_id",
      "label": "Block",
      "info": "Choose a Layers recommendation block.",
      "options": [
        { "value": "", "label": "Select a block" }
      ]
    }
  ],
  "presets": [{ "name": "Layers Recommendations" }]
}
{% endschema %}
The select options are populated from block metaobjects — see the Fetch guide for the pattern that mirrors metaobjects into the dropdown so merchandisers pick by name instead of pasting a ULID.

Hydrating the section

For each rendered section, create a client.blocks() controller with the selected handle and the page’s anchor. Subscribe to the state signal and render on data changes.
function hydrate(section) {
  const blockId = section.dataset.blockId
  const anchorId = section.dataset.anchorId || undefined
  const list = section.querySelector('.layers-recommendations__products')

  const blocks = window.layers.blocks({ blockId, anchor: anchorId })

  blocks.subscribe(({ data, error }) => {
    if (error || (data && !data.products.length)) {
      section.hidden = true
      return
    }
    if (data) renderProducts(list, data.products)
  })

  blocks.execute({ limit: 12 })
}

document.querySelectorAll('.layers-recommendations').forEach(hydrate)
function renderProducts(list, products) {
  list.removeAttribute('data-loading')
  list.innerHTML = products
    .map(
      (p) => `
        <li>
          <a href="/products/${p.handle}">
            <img src="${p.featured_media?.preview_image?.src ?? ''}" alt="" />
            <h3>${p.title}</h3>
            <span>${p.priceRange?.formatted ?? ''}</span>
          </a>
        </li>
      `,
    )
    .join('')
}
That’s the whole integration. Multiple sections on the same page each get their own controller, run independently, and reuse the SDK’s cache and dedup machinery.

What the app embed handles for you

When the Layers theme app extension is enabled, the storefront-side embed augments every Blocks request — including the SDK’s — so you don’t have to:
  • Cart context — current cart line items are sent automatically; cart-anchored blocks work without you reading /cart.js.
  • Product deduplication — products rendered earlier on the page are excluded from later blocks automatically.
  • Click attribution and feedback — the embed listens for clicks on rendered cards and submits feedback to keep ranking models fresh.
  • Identity — the storefront pixel manages deviceId and sessionId.
Your section only renders the placeholder and creates a controller. Everything else is managed for you.

Anchors

client.blocks({ blockId, anchor }) resolves the right value based on template:
Templateanchor
product{{ product.id }}
collection{{ collection.handle }}
Anything elseomitted (cart-anchored and global blocks resolve without one)
Both numeric IDs and Shopify GIDs are accepted on product anchors; both IDs and handles are accepted on collection anchors.

Why the SDK over Fetch

ConcernFetch versionSDK version
Driving the requestfetch to /blocks/{id}/productsblocks.execute()
Re-rendersDIY DOM updatesSignal subscriber
Caching across navDIYRestored from localStorage
Multiple blocks per pageManual orchestrationIndependent controllers, SDK dedupes identical requests
Cart context, dedup, and attribution behavior is identical between the two — the theme app extension does that work either way.

Troubleshooting

The section renders but the grid stays empty. The block fell through its fallback chain. Review fallback configuration in the dashboard. Block-picker dropdown is empty. Confirm block metaobjects are visible under Content → Metaobjects in Shopify admin, and that you’ve populated the section setting from those metaobjects (see the Fetch guide for the pattern). Cart-anchored blocks don’t refresh on cart changes. The theme app extension re-runs cart-anchored blocks. Confirm it’s enabled under Online Store → Themes → Customize → App embeds.

See also