---
title: Support storefront filtering
description: Learn how to support storefront filtering in themes.
source_url:
  html: >-
    https://shopify.dev/docs/storefronts/themes/navigation-search/filtering/storefront-filtering/support-storefront-filtering
  md: >-
    https://shopify.dev/docs/storefronts/themes/navigation-search/filtering/storefront-filtering/support-storefront-filtering.md
api_name: liquid
---

# Support storefront filtering

Storefront filtering allows merchants to [easily create filters](https://help.shopify.com/manual/online-store/themes/customizing-themes/storefront-filters#add-filters) for filtering collection and search results pages. Filters are based on existing product data, like availability, price, variant options, and more.

In this tutorial, you'll learn how to support storefront filtering in your theme.

***

## Resources

To implement storefront filtering, you'll use the following:

* The [`filter` object](https://shopify.dev/docs/api/liquid/objects/filter)

* One of the following, depending on the page you're working with:

  * The [`collection` object](https://shopify.dev/docs/api/liquid/objects/collection)
  * The [`search` object](https://shopify.dev/docs/api/liquid/objects/search)

***

## Implementing storefront filtering

To support storefront filtering, you need to implement a [filter display](#the-filter-display) for customers to interact with.

Applied filters are reflected through URL parameters, so you should also familiarize yourself with the structure of [filter URL parameters](https://shopify.dev/docs/storefronts/themes/navigation-search/filtering/storefront-filtering#filter-url-parameters).

**Note:**

Before filters can be applied, they need to be created in the Shopify admin.

***

## The filter display

**Note:**

Collections that contain over 5,000 products don't display filters.

The following sections outline a basic [collection](#collection-filter-display) and [search results](#search-results-filter-display) filter implementation. Each implementation uses a form to host the filter inputs with "submit" buttons to apply the associated filters. However, you can use JavaScript to automatically submit the form based on input changes. When the form is submitted, the page is refreshed with the filters applied.

For a more in-depth solution, refer to [Dawn's implementation](https://github.com/Shopify/dawn/blob/main/sections/main-collection-product-grid.liquid).

**Tip:**

Refer to [Storefront filtering UX guidelines](https://shopify.dev/docs/storefronts/themes/navigation-search/filtering/storefront-filtering/storefront-filtering-ux) for more information on UX best practices when building a filter display.

### Collection filter display

The collection filter display should be included in the [`collection` template](https://shopify.dev/docs/storefronts/themes/architecture/templates/collection), or a section that's included as part of the `collection` template.

The following example implementation contains two main components:

* A list of filter groups and values.

* A list of active filters, if there are any.

  Each of these components are output through the `filters` attribute of the `collection` object, and the associated `filter` objects.

## Example collection filter display

```liquid
<form>
  {%- for filter in collection.filters -%}
    <details>
      <summary>
        <div>
          <span>{{ filter.label }}</span>


          {%- if filter.active_values.size > 0 -%}
            <span>({{ filter.active_values.size }})</span>
          {%- endif -%}
        </div>
      </summary>


      <div>
        <div>
          <p>{{ filter.active_values.size }} selected</p>
          {%- if filter.active_values.size > 0 -%}
            <p><a href="{{ filter.url_to_remove }}">Reset</a></p>
          {%- endif -%}
        </div>
        {%- case filter.type -%}
          {%- when 'boolean' -%}
            <ul>
                <li>
                  <label for="Filter-{{ filter.param_name }}-{{ filter.true_value.value }}">
                  <input type="checkbox"
                    name="{{ filter.param_name }}"
                    value="{{ filter.true_value.value }}"
                    id="Filter-{{ filter.param_name }}"
                    {% if filter.true_value.active -%}checked{%- endif %}
                    {% if filter.true_value.count == 0 and filter.true_value.active == false -%}disabled{%- endif -%}
                   />{{ filter.true_value.label }}</label>
                </li>
                <li>
                  <label for="Filter-{{ filter.param_name }}-{{ filter.false_value.value }}">
                  <input type="checkbox"
                    name="{{ filter.param_name }}"
                    value="{{ filter.false_value.value }}"
                    id="Filter-{{ filter.param_name }}"
                    {% if filter.false_value.active -%}checked{%- endif %}
                    {% if filter.false_value.count == 0 and filter.false_value.active == false -%}disabled{%- endif %}
                   />{{ filter.false_value.label }}</label>
                </li>
            </ul>


            <div>
              <input type="submit" value="Apply" />
            </div>
          {%- when 'list' -%}
            <ul>
              {%- for filter_value in filter.values -%}
                <li>
                  <label for="Filter-{{ filter.param_name }}-{{ forloop.index }}">
                  <input type="checkbox"
                    name="{{ filter_value.param_name }}"
                    value="{{ filter_value.value }}"
                    id="Filter-{{ filter.param_name }}-{{ forloop.index }}"
                    {% if filter_value.active -%}checked{%- endif %}
                    {% if filter_value.count == 0 and filter_value.active == false -%}disabled{%- endif %}
                   />
                    <span>
                      {%- case filter_value.display.type -%}
                        {%- when 'colors' -%}
                          {% liquid
                            assign size_limit = filter_value.display.value.size | at_most: 4
                            assign rotation = '0deg'
                            if size_limit == 2
                              assign rotation = '45deg'
                            endif


                            assign angle_increment = 360 | divided_by: size_limit
                            assign angle = 0
                          %}
                          {%- capture conic_gradient -%}
                            {%- for color in filter_value.display.value limit: size_limit -%}
                              {{ color }} {{ angle }}deg{%- assign angle = angle | plus: angle_increment %} {{ angle }}deg{%- unless forloop.last %}, {%- endunless -%}
                            {%- endfor -%}
                          {%- endcapture -%}
                          <span style={{width: '25px', height: '25px', borderRadius: '50%', display: 'flex', justifyContent: 'center', alignItems: 'center', background: 'conic-gradient({{ conic_gradient }})', transform: 'rotateZ({{ rotation }})'}}></span>
                        {%- when 'image' -%}
                          {{
                            filter_value.display.value
                            | image_url: width: 25
                            | image_tag: alt: filter_value.display.value.alt
                          }}
                        {%- else -%}
                          <span className="visual-display__child"></span>
                      {%- endcase -%}
                    </span>
                    {{ filter_value.label }}
                  </label>
                </li>
              {%- endfor -%}
            </ul>


            <div>
              <input type="submit" value="Apply" />
            </div>
          {%- when 'price_range' -%}
            <div className="filter-group-display__price-range">
              <div className="filter-group-display__price-range-from">
                <span>{{ cart.currency.symbol }}</span>


                <input name="{{ filter.min_value.param_name }}"
                  id="Filter-{{ filter.min_value.param_name }}"
                  {% if filter.min_value.value -%}
                    value="{{ filter.min_value.value | money_without_currency | replace: ',', '' }}"
                  {%- endif %}
                  type="number"
                  placeholder="0"
                  min="0"
                  max="{{ filter.range_max | money_without_currency | replace: ',', '' }}"
                 />


                <label for="Filter-{{ filter.min_value.param_name }}">From</label>
              </div>
              <div className="filter-group-display__price-range-to">
                <span>{{ cart.currency.symbol }}</span>


                <input name="{{ filter.max_value.param_name }}"
                  id="Filter-{{ filter.max_value.param_name }}"
                  {% if filter.max_value.value -%}
                    value="{{ filter.max_value.value | money_without_currency | replace: ',', '' }}"
                  {%- endif %}
                  type="number"
                  placeholder="{{ filter.range_max | money_without_currency | replace: ',', '' }}"
                  min="0"
                  max="{{ filter.range_max | money_without_currency | replace: ',', '' }}"
                 />


                <label for="Filter-{{ filter.max_value.param_name }}">To</label>
              </div>
            </div>


            <div className="filter-group-display__submit">
              <input type="submit" value="Apply" />
            </div>
        {%- endcase -%}
      </div>
    </details>
  {%- endfor -%}


  <div>
    <p><a href="{{ collection.url }}?sort_by={{ collection.sort_by }}">Clear all</a></p>


    {%- for filter in collection.filters -%}
      {%- if filter.type == "price_range" -%}
        {%- if filter.min_value.value != nil or filter.max_value.value != nil -%}
          <p>
            <a href="{{ filter.url_to_remove }}">
              {%- assign min_value = filter.min_value.value | default: 0 -%}
              {%- assign max_value = filter.max_value.value | default: filter.range_max -%}
              {{ min_value | money }} - {{ max_value | money }} X
            </a>
          </p>
        {%- endif -%}
      {%- else -%}
        {%- for filter_value in filter.active_values -%}
          <p>
            <a href="{{ filter_value.url_to_remove }}">
              {{ filter.label }}: {{ filter_value.label }} X
            </a>
          </p>
        {%- endfor -%}
      {%- endif -%}
    {%- endfor -%}
  </div>
</form>
```

### Search results filter display

**Note:**

Filters on the search results page are calculated from up to the first 1,000 search results.

The search results filter display should be included in the [`search` template](https://shopify.dev/docs/storefronts/themes/architecture/templates/search), or in a section that's included as part of the `search` template.

The following example implementation contains two main components:

* A list of filter groups and values.

* A list of active filters, if there are any.

  Each of these components are output through the `filters` attribute of the `search` object, and the associated `filter` objects.

  If you apply filters on the search results page, then all non-product results are filtered out.

## Example search results filter display

```liquid
<form>
  <input type="hidden" name="q" value="{{ search.terms }}" />


  {%- for filter in search.filters -%}
    <details>
      <summary>
        <div>
          <span>{{ filter.label }}</span>


          {%- if filter.active_values.size > 0 -%}
            <span>({{ filter.active_values.size }})</span>
          {%- endif -%}
        </div>
      </summary>


      <div>
        <div>
          <p>{{ filter.active_values.size }} selected</p>
          {%- if filter.active_values.size > 0 -%}
            <p><a href="{{ filter.url_to_remove }}">Reset</a></p>
          {%- endif -%}
        </div>
        {%- case filter.type -%}
          {%- when 'boolean' -%}
            <ul>
                <li>
                  <label for="Filter-{{ filter.param_name }}-{{ filter.true_value.value }}">
                  <input type="checkbox"
                    name="{{ filter.param_name }}"
                    value="{{ filter.true_value.value }}"
                    id="Filter-{{ filter.param_name }}"
                    {% if filter.true_value.active -%}checked{%- endif %}
                    {% if filter.true_value.count == 0 and filter.true_value.active == false -%}disabled{%- endif -%}
                   />{{ filter.true_value.label }}</label>
                </li>
                <li>
                  <label for="Filter-{{ filter.param_name }}-{{ filter.false_value.value }}">
                  <input type="checkbox"
                    name="{{ filter.param_name }}"
                    value="{{ filter.false_value.value }}"
                    id="Filter-{{ filter.param_name }}"
                    {% if filter.false_value.active -%}checked{%- endif %}
                    {% if filter.false_value.count == 0 and filter.false_value.active == false -%}disabled{%- endif %}
                   />{{ filter.false_value.label }}</label>
                </li>
            </ul>


            <div>
              <input type="submit" value="Apply" />
            </div>
          {%- when 'list' -%}
            <ul>
              {%- for filter_value in filter.values -%}
                <li>
                  <label for="Filter-{{ filter.param_name }}-{{ forloop.index }}">
                  <input type="checkbox"
                    name="{{ filter_value.param_name }}"
                    value="{{ filter_value.value }}"
                    id="Filter-{{ filter.param_name }}-{{ forloop.index }}"
                    {% if filter_value.active -%}checked{%- endif %}
                    {% if filter_value.count == 0 and filter_value.active == false -%}disabled{%- endif %}
                   />
                    <span>
                      {%- case filter_value.display.type -%}
                        {%- when 'colors' -%}
                          {% liquid
                            assign size_limit = filter_value.display.value.size | at_most: 4
                            assign rotation = '0deg'
                            if size_limit == 2
                              assign rotation = '45deg'
                            endif


                            assign angle_increment = 360 | divided_by: size_limit
                            assign angle = 0
                          %}
                          {%- capture conic_gradient -%}
                            {%- for color in filter_value.display.value limit: size_limit -%}
                              {{ color }} {{ angle }}deg{%- assign angle = angle | plus: angle_increment %} {{ angle }}deg{%- unless forloop.last %}, {%- endunless -%}
                            {%- endfor -%}
                          {%- endcapture -%}
                          <span style={{width: '25px', height: '25px', borderRadius: '50%', display: 'flex', justifyContent: 'center', alignItems: 'center', background: 'conic-gradient({{ conic_gradient }})', transform: 'rotateZ({{ rotation }})'}}></span>
                        {%- when 'image' -%}
                          {{
                            filter_value.display.value
                            | image_url: width: 25
                            | image_tag: alt: filter_value.display.value.alt
                          }}
                        {%- else -%}
                          <span className="visual-display__child"></span>
                      {%- endcase -%}
                    </span>
                    {{ filter_value.label }}
                  </label>
                </li>
              {%- endfor -%}
            </ul>


            <div>
              <input type="submit" value="Apply" />
            </div>
          {%- when 'price_range' -%}
            <div className="filter-group-display__price-range">
              <div className="filter-group-display__price-range-from">
                <span>{{ cart.currency.symbol }}</span>


                <input name="{{ filter.min_value.param_name }}"
                  id="Filter-{{ filter.min_value.param_name }}"
                  {% if filter.min_value.value -%}
                    value="{{ filter.min_value.value | money_without_currency | replace: ',', '' }}"
                  {%- endif %}
                  type="number"
                  placeholder="0"
                  min="0"
                  max="{{ filter.range_max | money_without_currency | replace: ',', '' }}"
                 />


                <label for="Filter-{{ filter.min_value.param_name }}">From</label>
              </div>
              <div className="filter-group-display__price-range-to">
                <span>{{ cart.currency.symbol }}</span>


                <input name="{{ filter.max_value.param_name }}"
                  id="Filter-{{ filter.max_value.param_name }}"
                  {% if filter.max_value.value -%}
                    value="{{ filter.max_value.value | money_without_currency | replace: ',', '' }}"
                  {%- endif %}
                  type="number"
                  placeholder="{{ filter.range_max | money_without_currency | replace: ',', '' }}"
                  min="0"
                  max="{{ filter.range_max | money_without_currency | replace: ',', '' }}"
                 />


                <label for="Filter-{{ filter.max_value.param_name }}">To</label>
              </div>
            </div>


            <div className="filter-group-display__submit">
              <input type="submit" value="Apply" />
            </div>
        {%- endcase -%}
      </div>
    </details>
  {%- endfor -%}


  <div>
    <p><a href="{{ routes.search_url }}?sort_by={{ search.sort_by }}&q={{ search.terms }}">Clear all</a></p>
    {%- for filter in search.filters -%}
      {%- if filter.type == "price_range" -%}
        {%- if filter.min_value.value != nil or filter.max_value.value != nil -%}
          <p>
            <a href="{{ filter.url_to_remove }}">
              {%- assign min_value = filter.min_value.value | default: 0 -%}
              {%- assign max_value = filter.max_value.value | default: filter.range_max -%}
              {{ min_value | money }} - {{ max_value | money }} X
            </a>
          </p>
        {%- endif -%}
      {%- else -%}
        {%- for filter_value in filter.active_values -%}
          <p>
            <a href="{{ filter_value.url_to_remove }}">
              {{ filter.label }}: {{ filter_value.label }} X
            </a>
          </p>
        {%- endfor -%}
      {%- endif -%}
    {%- endfor -%}
  </div>
</form>
```

***
