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

Layers facet metaobjects describe which filters are configured for your store (their name and code), but they do not contain the runtime values, counts, or numeric ranges available for a given collection. To render a working filter sidebar, combine the two data sources:
  1. Read facet metaobjects in Liquid to get the list of configured facets and their codes.
  2. Pass those codes to the Facets API for the current collection.
  3. The API returns only the facet values that exist in that collection, along with counts and min/max ranges.
This pattern keeps your filter UI in sync with merchandising config (facets enabled, ordered, and named in Layers) while never rendering empty or unavailable values.

Prerequisites

  • The Layers Shopify app installed and synced.
  • Facets configured in the Layers dashboard with Enable as Storefront Facet turned on.
  • A storefront access token (available via shop.metafields.layers.embed_settings). See Liquid integration for full setup.

The pattern

1

Read facet metaobjects in Liquid

Each facet metaobject exposes a name (display label) and code (the attribute code passed to the API).
{% assign layers_facets = shop.metaobjects['app--278936322049--facet'] %}

<aside class="filters" data-collection="{{ collection.handle }}">
  <h2>Filter by</h2>
  {% for facet in layers_facets %}
    <section
      class="filter-group"
      data-facet-code="{{ facet.code.value }}"
    >
      <h3>{{ facet.name.value }}</h3>
      <div class="filter-values" data-loading="true"></div>
    </section>
  {% endfor %}
</aside>
2

Collect every facet code and pass it to the API

Build the request body from the codes rendered server-side. Set retrieveFacetCount: true to get per-value counts and includeFacetRanges: true to get min/max for numeric facets (e.g. price).
const sidebar = document.querySelector('.filters');
const collectionHandle = sidebar.dataset.collection;

const facetCodes = Array.from(
  sidebar.querySelectorAll('[data-facet-code]')
).map((el) => el.dataset.facetCode);

const response = await fetch(
  `https://app.uselayers.com/storefront/v1/${collectionHandle}/facets`,
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Storefront-Access-Token': window.layersConfig.storefrontApiToken,
    },
    body: JSON.stringify({
      facets: facetCodes,
      retrieveFacetCount: true,
      includeFacetRanges: true,
    }),
  }
);

const { facets, facetRanges } = await response.json();
The response only includes values that exist for products in {collection_handle}. Facets with no matching products are omitted, so you never render dead-end filters.
3

Render values, counts, and ranges

Walk the rendered facet groups and populate each one based on whether the API returned a value map (facets) or a numeric range (facetRanges).
sidebar.querySelectorAll('[data-facet-code]').forEach((group) => {
  const code = group.dataset.facetCode;
  const container = group.querySelector('.filter-values');
  container.removeAttribute('data-loading');

  if (facetRanges?.[code]) {
    const { min, max } = facetRanges[code];
    container.innerHTML = `
      <input type="range" name="${code}_min" min="${min}" max="${max}" value="${min}" />
      <input type="range" name="${code}_max" min="${min}" max="${max}" value="${max}" />
      <output>${min}${max}</output>
    `;
    return;
  }

  const values = facets?.[code];
  if (!values) {
    group.hidden = true; // no available values for this collection
    return;
  }

  container.innerHTML = Object.entries(values)
    .map(
      ([value, count]) => `
        <label>
          <input type="checkbox" name="${code}" value="${value}" />
          ${value} <span class="count">(${count})</span>
        </label>
      `
    )
    .join('');
});

Counts and ranges

The Facets API returns two complementary shapes:
FieldWhen returnedShapeUse for
facetsretrieveFacetCount: true{ [code]: { [value]: count } }Checkbox / pill filters where each option shows a result count
facetRangesincludeFacetRanges: true{ [code]: { min, max } }Numeric facets (price, weight, rating) rendered as sliders or min/max inputs
A single request can return both — numeric facets land in facetRanges, categorical ones in facets.
Example response
{
  "facets": {
    "vendor": { "Nike": 45, "Adidas": 30, "Puma": 20 },
    "options.Size": { "S": 22, "M": 31, "L": 28 }
  },
  "facetRanges": {
    "price_range": { "min": 29.99, "max": 249.99 }
  }
}

Wildcards

If you have many option or metafield facets, you can pass wildcard patterns instead of enumerating every code:
{
  "facets": ["vendor", "options.*", "metafields.product.*"],
  "retrieveFacetCount": true,
  "includeFacetRanges": true
}
Use this when you want every option (options.*) or every product metafield (metafields.product.*) without keeping the theme in sync with attribute changes. Wildcards must match at least one attribute code.

Keeping counts accurate as filters are applied

To narrow counts as the customer applies filters, send the active filter selections as a filter_group on the same endpoint. The response counts then reflect only products that match the current selection. See Filter expressions for the full filter grammar.
await fetch(`https://app.uselayers.com/storefront/v1/${collectionHandle}/facets`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Storefront-Access-Token': window.layersConfig.storefrontApiToken,
  },
  body: JSON.stringify({
    facets: facetCodes,
    retrieveFacetCount: true,
    includeFacetRanges: true,
    filter_group: {
      operator: 'AND',
      conditions: [
        { attribute: 'vendor', operator: 'IN', value: ['Nike'] },
      ],
    },
  }),
});

When to use the Facets API vs. Browse

  • Use the Facets API when you only need filter data — for example, pre-rendering the sidebar before any product results load, or refreshing counts as the customer toggles filters.
  • Use the Browse API with retrieveFacetCount and includeFacetRanges when you need products and facets in a single round trip (typical for the initial collection page load).

Troubleshooting

Facet metaobjects are empty in Liquid. Confirm the facet has Enable as Storefront Facet turned on in the Layers dashboard and that the metaobject is visible under Content → Metaobjects in Shopify admin. A facet is missing from the API response. The Facets API omits facets with no matching values for the current collection. Hide the corresponding group in the UI (as in the example above) rather than treating it as an error. Counts look wrong after applying filters. Make sure you are re-requesting the Facets API with an updated filter_group every time the active selection changes.

See also