Skip to main content

Overview

This is the SDK equivalent of Rendering facets in Liquid. The pattern is identical — read facet metaobjects in Liquid, then resolve runtime facet values, counts, and ranges from Layers — but the SDK’s client.collection() controller handles the HTTP request, request deduplication, caching, abort signals, and filter-group transformation for you, and exposes results as a reactive signal you can subscribe to. Use this version when you’ve already installed @commerce-blocks/sdk in your theme. If you’re scripting against the storefront API directly with fetch, see the vanilla Liquid + Fetch guide instead.

Prerequisites

  • The Layers Shopify app installed and synced.
  • Facets configured in the Layers dashboard with Enable as Storefront Facet turned on.
  • The SDK installed and a client created in your theme. See SDK installation.
Generating the sorts and facets arrays from Liquid metaobjects keeps your client config in sync with the dashboard — adding, renaming, or hiding a facet in the Layers dashboard updates the storefront on the next render without a theme deploy.

The pattern

1

Read facet metaobjects in Liquid

Render the sidebar shell from metaobjects exactly like the Fetch version. Each <section> carries the facet code so the SDK results can be matched to the right group.
2

Create a collection controller

client.collection() returns a reactive controller. Its state signal updates every time execute() runs, and the SDK deduplicates identical in-flight requests and caches results in localStorage.
The SDK request includes facet counts and ranges by default — there’s no retrieveFacetCount / includeFacetRanges flag to set. data.facets is the value-count map, and numeric ranges are exposed on top-level fields like data.priceRange (when variants.price is in your facets config).
3

Render values, counts, and ranges

The render code looks at the same data-facet-code attributes as the Fetch version.
4

Execute the initial load

Trigger the first request. The subscriber above renders the sidebar as soon as data lands.

Refreshing counts as filters are applied

Call execute({ filters }) whenever the active selection changes. The SDK reuses the same controller, dedupes against in-flight requests, and updates the state signal — subscribers re-render automatically.
If you’ve configured filter aliases on the client (e.g. color → options.color), the SDK resolves them automatically — you can keep the URL- and UI-friendly keys in your form names.

Same-facet vs. cross-facet counting

Multi-value selections (arrays, resolved as IN / NOT IN) follow the standard faceted-search pattern of “OR within a facet, AND across facets”:
  • Multi-select filters are counted against every active filter except the facet’s own condition, so other values in the same facet remain selectable with the counts the shopper would see if they added them. Selecting “Swim Bottoms” in product_type keeps “Swim Tops” visible.
  • Single-select and predicate filters (e.g. EQ, BETWEEN, NULL) are counted against the fully filtered result set, so the facet reflects only the currently narrowed products.
The Facets API applies the correct behavior per facet based on the operator — pass every active selection in execute({ filters }) and render every value returned in data.facets[code].

Why the SDK over Fetch

If you have any one of these needs — caching, dedup, abort, reactive UI — the SDK saves you the boilerplate. The Fetch version remains useful when you want zero dependencies.

Troubleshooting

data.facets is empty. Confirm the facets you expect are listed in the facets array passed to createClient. The SDK only requests facet codes it knows about. Counts don’t change when filters are applied. Make sure you’re passing the active filters to execute({ filters }) — the SDK doesn’t watch the DOM. Subscribe to the controller state and re-render on every emit. data.priceRange is undefined. priceRange requires { name: 'Price', code: 'variants.price' } in your facets config. See Responses and errors.

See also