Fixes · Collection filters

Shopify size filter shows products that are out of stock in that size

The fix

On its own, the Size filter shows any product that has the size as a variant, in stock or not. Tick the Availability filter’s In stock as well and Shopify checks stock in that size, but shoppers rarely tick it.

So have the theme do it: apply In stock whenever a size is picked, or skip products with no in-stock variant in the chosen size and leave sizes with nothing in stock out of the list. Or use a filter app that indexes each variant.

01Why a size filter doesn’t check stock

Shopify’s storefront filtering can filter by variant options such as Size, and by availability. Filters combine with AND, and the values within one filter with OR (Shopify, Storefront filtering). A Size filter asks whether the product has a variant whose size is the one chosen. A variant in that size that’s sold out still counts.

“A size filter matches any product that HAS that size, regardless of whether that size is in stock.”

Availability is a filter of its own. Shopify describes it as filtering “based on variant availability”, and doesn’t say whether it’s checked against the same variant as the size. Replies on that 2026 thread say it isn’t: that a product still passes for a sold-out size because another size is in stock. That isn’t what we found. On a live store we look after (22 Oct 2026), a product sold out only in XS showed for Size XS, dropped out once In stock was ticked too, and came back with Out of stock. A dress sold out in S but not in L dropped out for S and In stock, and stayed for L and In stock. So Shopify does match the two to the same variant, but only when the shopper ticks both, and few do.

The filter list has the same blind spot. Without In stock it offers every size any product in the collection carries, so a shopper can pick a size that leads to nothing. With In stock ticked, the same test showed every size with nothing in stock counting zero, so a theme that hides empty values drops them. On an older thread (Jun 2023) the answers are Flow and metafields, an app, or theme code, and none is marked as the solution.

02Make picking a size mean in stock

For store owners

  1. See it for yourself

    On a collection, choose a size, then open a few of the products and check that size is in stock on each.

  2. Add the Availability filter, and know its limit

    In the Search & Discovery app, under Filters, add Availability. A shopper who ticks In stock with a size gets only products in stock in that size. One who doesn’t still sees the sold-out ones.

  3. Hide empty sizes

    In Search & Discovery > Settings, under Filters > Empty values, choose Hide (Shopify Help). With In stock ticked, a size with nothing in stock is then left out, if your theme follows the setting.

  4. Choose the fix

    A change to your theme (below) keeps Shopify’s own filters and costs nothing to run. The simplest applies In stock for the shopper whenever they pick a size. A filter app that indexes each variant is the other way.

For developers: filter the grid by stock, and drop dead-end sizes

The simplest route: when a shopper picks a size, tick In stock for them, so Shopify does the matching. Its pages and counts then agree with the grid, with no limit on collection size. Availability has to be one of the store’s filters for the input to exist. In a theme whose filters are checkboxes in one form:

Picking a size ticks In stock toojs
const form = document.querySelector('#FacetFiltersForm'); // your theme's filter form
form.addEventListener('input', (event) => {
  const input = event.target;
  if (input.name !== 'filter.v.option.size' || !input.checked) return;
  const inStock = form.querySelector('input[name="filter.v.availability"][value="1"]');
  if (inStock) inStock.checked = true;
}, true); // capture: runs before the theme reads the form

The shopper sees In stock among the active filters and can untick it. If you’d rather not change the filters they chose, filter in the theme instead. That’s what we shipped on the store below. Two changes in the collection section. First, in the product loop, skip any product with no in-stock variant in the chosen sizes:

The grid: only products in stock in the chosen sizeliquid
{%- liquid
  assign size_filter = collection.filters | where: 'param_name', 'filter.v.option.size' | first
  assign wanted = size_filter.active_values | map: 'value'
-%}
{%- for product in collection.products -%}
  {%- if wanted.size > 0 -%}
    {%- assign size_option = product.options_by_name['Size'] -%}
    {%- assign in_stock = false -%}
    {%- if size_option -%}
      {%- assign index = size_option.position | minus: 1 -%}
      {%- for variant in product.variants -%}
        {%- if variant.available and wanted contains variant.options[index] -%}
          {%- assign in_stock = true -%}
          {%- break -%}
        {%- endif -%}
      {%- endfor -%}
    {%- endif -%}
    {%- unless in_stock -%}
      {%- continue -%}
    {%- endunless -%}
  {%- endif -%}
  {%- render 'product-card', product: product -%}
{%- endfor -%}

Second, in the filter list, leave out sizes that nothing in the collection has in stock:

The Size list: only sizes with something in stockliquid
{%- liquid
  assign instock_sizes = ''
  if collection.all_products_count <= 250
    paginate collection.products by 250
      for product in collection.products
        assign size_option = product.options_by_name['Size']
        if size_option
          assign index = size_option.position | minus: 1
          for variant in product.variants
            if variant.available
              assign key = '|' | append: variant.options[index] | append: '|'
              unless instock_sizes contains key
                assign instock_sizes = instock_sizes | append: key
              endunless
            endif
          endfor
        endif
      endfor
    endpaginate
  endif
-%}
{%- for filter in collection.filters -%}
  {%- if filter.param_name == 'filter.v.option.size' -%}
    {%- for value in filter.values -%}
      {%- assign key = '|' | append: value.value | append: '|' -%}
      {%- if instock_sizes != blank and value.active == false -%}
        {%- unless instock_sizes contains key -%}
          {%- continue -%}
        {%- endunless -%}
      {%- endif -%}
      <label>
        <input type="checkbox" name="{{ value.param_name }}" value="{{ value.value }}" {% if value.active %}checked{% endif %}>
        {{ value.label }}
      </label>
    {%- endfor -%}
  {%- endif -%}
{%- endfor -%}
  • Match the size by its position. product.options_by_name['Size'].position says which of the variant’s options is the size (Shopify, the product object), so a value in another option can’t match by accident. Use the option name your store uses, in both places.
  • Liquid gives you at most 250 products per page (Shopify, paginate). The in-stock size list needs the whole collection in one pass, so build it only up to 250 products; bigger collections keep Shopify’s list and rely on the grid.
  • The second paginate reads the same ?page= as the grid. On page 2 of a collection under 250 products it holds nothing, so the list must fall back to Shopify’s when the set comes back empty (instock_sizes != blank above). Without that check, page 2 shows no sizes at all.
  • collection.products is already narrowed by the filters the shopper has ticked, so the list reflects those products. Once a size is ticked, another size shows only if a product that matched stocks it.
  • Skipping products doesn’t change Shopify’s counts or pages: a page can show fewer products, and the number beside each size still counts sold-out ones. Show your “no products” message when every product on a page is skipped and there’s no next page, or the grid sits blank.
  • Keep a size the shopper has ticked in the list, even if it has sold out, so they can untick it (value.active above).

03Try a sold-out size, then your biggest collection

  1. Pick a size that’s sold out on some products

    Those products shouldn’t appear; the ones in stock in that size should.

  2. Open the size list

    A size with nothing in stock in the collection shouldn’t be offered.

  3. Link straight to a sold-out size

    Add ?filter.v.option.size= and the size to the collection’s address. You should get the “no products” message, not a blank grid.

  4. Try your biggest collection

    Over 250 products, the list stays Shopify’s and the grid still filters.

From our work

04A sportswear retailer’s outlet collection

On a sportswear retailer’s store we look after, the store’s team reported that the Size filter on its outlet collection showed products that weren’t available in the chosen size.

After (15 Sep 2026). The grid now skips any product with no in-stock variant in the chosen sizes. The Size list drops sizes with nothing in stock on collections of up to 250 products. The outlet holds more than that, so there the list stays Shopify’s and the grid does the work. When skipping leaves nothing on a page, the store’s “no products” message shows instead of a blank grid.

  • In our log“Size filter now shows only products in stock in the chosen size”

The bigger picture

05A filter is a promise

A filter is a promise about what a shopper can buy. If your collections are large, change every day or carry many sizes, check what each filter actually returns, and what search returns too.

We’d make this change in the theme and keep Shopify’s own filters, rather than add an app to do it.

06Sources

Every step and claim above comes from one of these, or from the fix on the store it describes.

  1. Shopify developers: Storefront filtering
  2. Shopify Help Center: Search & Discovery filters (empty values)
  3. Shopify developers: Liquid objects, product
  4. Shopify developers: Liquid tags, paginate
  5. Shopify Community: Impulse theme, size filter shows out-of-stock sizes and unavailable variants (Jul 2026)
  6. Shopify Community: Filter showing out of stock sizes, Dawn theme (Jun 2023)

Sources checked on .

Contact

Filters showing what nobody can buy?

Send us a collection link and the size that goes wrong.