> ## 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.

# Rendering facets in Liquid

> Render filter sidebars in a Shopify theme by combining Layers facet metaobjects with the Facets API so only available values, counts, and ranges are shown.

## 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](/api-reference/facets) 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](/developers/liquid-integration) for full setup.

## The pattern

<Steps>
  <Step title="Read facet metaobjects in Liquid">
    Each facet metaobject exposes a `name` (display label) and `code` (the attribute code passed to the API).

    ```liquid theme={null}
    {% 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>
    ```
  </Step>

  <Step title="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).

    ```js theme={null}
    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.
  </Step>

  <Step title="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`).

    ```js theme={null}
    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('');
    });
    ```
  </Step>
</Steps>

## Counts and ranges

The Facets API returns two complementary shapes:

| Field         | When returned              | Shape                            | Use for                                                                      |
| :------------ | :------------------------- | :------------------------------- | :--------------------------------------------------------------------------- |
| `facets`      | `retrieveFacetCount: true` | `{ [code]: { [value]: count } }` | Checkbox / pill filters where each option shows a result count               |
| `facetRanges` | `includeFacetRanges: 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`.

```json Example response theme={null}
{
  "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:

```json theme={null}
{
  "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](/sdk/api-reference/filtering) for the full filter grammar.

```js theme={null}
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](/api-reference/facets) 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](/api-reference/browse) 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

* [Liquid integration](/developers/liquid-integration) — reading sort orders, facets, blocks, and app settings from Liquid
* [Facets API](/api-reference/facets) — full request and response reference
* [Browse API](/api-reference/browse) — combined product results and facets
* [Filters / facets](/platform/facets) — configuring facets in the Layers dashboard
