Skip to main content

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. The SDK’s blocks() controller does the HTTP call, deduplicates in-flight requests, caches responses in memory for the life of the page, and exposes the products on reactive state. The SDK sends exactly the values your code supplies and returns results with an attributionToken for tracking. Use this version when you have 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 initialized in your theme. See SDK installation.

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 ID.
The select options are populated from block metaobjects. See the Fetch guide for the pattern that mirrors metaobjects into the dropdown so merchandisers select by name instead of pasting a ULID.
A block metaobject’s system handle is not the value to pass as blockId. Layers generates handles as layers-<lowercase block ID> (for example, layers-01j8abcd1234xyz), and the raw block ID is not stored in any metaobject field. Derive the ID in Liquid:
Passing the raw system.handle silently returns no products — the handle never matches a block ID.

Hydrating the section

For each rendered section, create an sdk.blocks() controller with the selected block ID and the page anchor. Subscribe to the state and render on data changes.
That is 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.

Rendering cards with your theme’s own Liquid snippets

A client-side card renderer drifts out of sync with the theme’s product card (badges, ratings, swatches, variant logic). The more durable pattern is Shopify’s Section Rendering API: create one thin section per surface that renders your existing card snippet for a single product, then fetch it once per result handle.
Each request returns { "<sectionId>": "<rendered HTML>" } — a map of section ID to its HTML fragment. A missing product or a section that renders nothing produces an empty string or a missing key, which is why the helper filters blank cards.
  • One request per result, fired in parallel — a failed fetch drops that card instead of breaking the rail.
  • Pass the product’s selectedVariant.numericId as the product-page variant parameter so the section renders the variant Layers selected (filters, ranking, variant breakouts) — without it Shopify renders first_available_variant and two breakout tiles of one product collapse into identical cards. The SDK always requests the matched variant and exposes it as selectedVariant; use numericId, because id is the layers--prefixed SDK ID. If you call the API with Fetch instead, the raw result carries it as first_or_matched_variant.id.
  • ?sections= accepts up to 5 comma-separated section IDs per request, so distinct surfaces can share a round trip.
  • Keep the section’s card arguments in sync with the native grid (rating settings, hide-tag skips, favorite button). The section is the single place that mapping lives.
  • Grid markup still comes from the Liquid section wrapping the rail; only the tile HTML is fetched.

Identity, context, and attribution

The app embed provides the storefront pixel and its standard theme tracking. Pass the values your block requests need through the SDK:
  • Identity: Pass identity to createSDK() as a static object or a resolver that runs on every request. On a pixel-driven storefront, read device and session identifiers from the storefront pixel and pass them through. The SDK does not read them for you.
  • Cart context and exclusions: Pass context and excludeProductIds on the request when you need cart-anchored blocks or cross-block product deduplication.
  • Click attribution: Results carry an attributionToken. The storefront pixel covers standard themes. For custom-rendered cards, forward attributionToken on your click and add-to-cart tracking.

Anchors

sdk.blocks({ blockId, anchorId }) resolves the right value based on template: Both numeric IDs and Shopify GIDs are accepted on product anchors; both IDs and handles are accepted on collection anchors.

Search-anchored blocks

Blocks on the search anchor running searched_then_viewed or searched_then_purchased strategies do not read the query from anchor_id. They resolve the shopper’s query from context.priorSearches — specifically the last searchQuery — so the caller must record submitted search queries and pass them on the request:
Without context.priorSearches, a search-anchored block has no query to work from and falls through to its fallback chain. On a first visit before any search has been submitted there is nothing to anchor to — give the block a fallback (trending, manual, or bestsellers) that still produces a rail on cold opens.

Why the SDK over Fetch

Both paths send exactly the cart context, exclusions, identity, and attribution values your code passes.

Troubleshooting

The section renders but the grid stays empty. The block fell through its fallback chain. Review fallback configuration in the dashboard. No Layers requests fire at all. Work this checklist in order: (1) <script id="layers-config"> is present in the page <head> with a non-empty storefrontApiToken — if the tag is missing or blank, enable the app embed (see SDK installation); (2) the block picker in the section settings is actually assigned — an unpicked setting silently no-ops; (3) the ID reaching blockId is the raw ULID, not the layers--prefixed metaobject handle (see the transform above). Block-picker dropdown is empty. Confirm block metaobjects are visible under Content → Metaobjects in Shopify admin, and that you have populated the section setting from those metaobjects (see the Fetch guide for the pattern). Cart-anchored blocks do not refresh on cart changes. The theme app extension re-runs cart-anchored blocks. Confirm it is enabled under Online Store → Themes → Customize → App embeds.

See also