Setelah pembeli masuk lewat menu di 36 Navigasi dan Menu, mereka biasanya mendarat di halaman koleksi berisi ratusan produk. Filtering itu yang bikin ratusan jadi sepuluh yang relevan.
Ada dua cara filter produk di theme Shopify:
- Storefront filtering, cara yang disarankan. Merchant bikin grup filter dari data produk yang udah ada (ketersediaan, harga, opsi varian, metafield) lewat app Search & Discovery, dan filternya berlaku di koleksi dan hasil pencarian.
- Tag filtering, cara lama pakai tag produk. Cuma buat koleksi, dan merchant harus bikin sistem tag manual.
Bab ini bahas keduanya, dengan porsi terbesar buat storefront filtering.
Storefront Filtering
Filter bisa dibikin dari data produk dan varian:
- Availability (ketersediaan)
- Category (kategori produk)
- Price (harga)
- Product tags
- Product type
- Vendor
- Variant options (warna, ukuran)
- Metafields
Logikanya: antar filter pakai AND, antar nilai dalam satu filter pakai OR. Jadi “warna merah dan ukuran L”, tapi “warna merah atau biru”. Merchant bisa bikin maksimal 25 filter.
Dua batasan penting: koleksi dengan lebih dari 5.000 produk gak nampilin filter, dan hasil pencarian dengan lebih dari 1.000 produk juga gak. Dan sebelum bisa dipakai, filternya harus dibikin dulu di admin.
URL Parameter Filter
Filter yang aktif tercermin di URL halaman. Ini bagus: URL bisa dibagiin, tombol back jalan, dan theme gak perlu nyimpen state sendiri. Strukturnya:
filter.filter_scope.attribute=value
filter.filter_scope.attribute.attribute_scope=valueKomponennya:
filter, namespace tetap.filter_scope,pbuat level produk,vbuat level varian.attribute, atribut yang difilter.attribute_scope(opsional), buatoption,price, dan metafield.value, nilai filternya.
Contoh filter tipe produk shoes dan opsi varian Color red di koleksi all:
/collections/all?filter.p.product_type=shoes&filter.v.option.color=redBanyak nilai buat satu filter bisa dua cara, hasilnya sama:
filter.v.option.color=red,blue
filter.v.option.color=red&filter.v.option.color=blue1. Filter Level Produk
- Category:
t.category, nilai satu ID kategori atau daftar dipisah__(aa-1atauaa-1__aa-2). - Product tags:
tag, satu tag atau daftar dipisah koma (newataunew,trending). - Product type:
product_type(shoesataushoes,belts). - Vendor:
vendor(vendor1atauvendor1,vendor2). - Metafield:
m+ namespace + key sebagaiattribute_scope. Metafieldcustom.made_injadim.custom.made_in, nilainyacanadaataucanada,usa. Tipe metafield yang didukung:single_line_text_field,list.single_line_text_field,metaobject_reference,list.metaobject_reference,number_integer,number_decimal,boolean.
// Product tag
filter.p.tag=new,trending
// Product type
filter.p.product_type=shoes
// Product vendor
filter.p.vendor=vendor1
// Product metafield
filter.p.m.custom.made_in=canadaCatatan buat metafield: daftar dipisah koma itu daftar nilai metafield individual, bukan satu nilai metafield yang isinya ada komanya.
2. Filter Level Varian
- Availability:
availability, nilai0(habis),1(tersedia), atau0,1(keduanya). - Variant option:
option+ nama opsi sebagaiattribute_scope, misalnyaoption.color, nilairedataured,blue. - Price:
price+ kondisi sebagaiattribute_scope:lte(kurang dari atau sama dengan) ataugte(lebih dari atau sama dengan). Nilainya angka dalam mata uang default toko (5atau20.40). - Standard product attribute:
t+shopify+ key, misalnyat.shopify.fabric. Ini metafield standarlist.metaobject_referenceyang nyambung ke opsi produk. Nilainya GID metaobject atau GID nilai taksonomi (gid://shopify/Metaobject/1). - Metafield varian:
m.custom.fabric, sama kaya metafield produk tapi di level varian.
// Variant availability
filter.v.availability=1
// Variant price
filter.v.price.lte=5
// Variant option
filter.v.option.color=red
// Standard product attribute
filter.v.t.shopify.fabric=gid://shopify/Metaobject/1
// Variant metafields
filter.v.m.custom.fabric=leatherEfek samping yang berguna: waktu filter varian aktif, product.featured_media dan product.url berubah buat nunjuk ke varian pertama yang cocok. Jadi kalau pembeli filter warna merah, kartu produk otomatis nampilin gambar varian merah dan link-nya deep link ke varian merah. Deep link varian dibahas di 39 Produk Variants dan High-variant Products.
Bikin Tampilan Filter
Yang kamu butuhin: object filter (lewat collection.filters atau search.filters), yang propertinya udah dibahas di 17 Objects Lainnya > Object Filtering. Ringkasnya:
filter.label, nama grup filter.filter.type:list,price_range, atauboolean.filter.param_name, nama parameter URL.filter.values, semua nilai (buatlist), tiap nilai punyalabel,value,count,active,param_name,url_to_add,url_to_remove,display.filter.active_values, nilai yang lagi aktif.filter.url_to_remove, URL tanpa filter ini.filter.min_value,filter.max_value,filter.range_maxbuatprice_range.filter.true_value,filter.false_valuebuatboolean.
Tampilan filter ditaruh di template collection (atau section di dalamnya), dan di template search. Implementasi dasarnya pakai form dengan tombol submit: waktu disubmit, halaman reload dengan filter di URL. JavaScript bisa ditambahin buat submit otomatis dan update grid tanpa reload lewat 51 Ajax API dan Section Rendering API, tapi dasarnya harus jalan tanpa JavaScript. Implementasi lengkapnya bisa dilihat di Dawn.
1. Form Filter Koleksi
Dua komponen: daftar grup filter dan nilainya, dan daftar filter aktif. Ini contoh lengkapnya, kita bedah bagian per bagian setelahnya:
<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; border-radius: 50%; display: flex; justify-content: center; align-items: 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 class="visual-display__child"></span>
{%- endcase -%}
</span>
{{ filter_value.label }}
</label>
</li>
{%- endfor -%}
</ul>
<div>
<input type="submit" value="Apply" />
</div>
{%- when 'price_range' -%}
<div class="filter-group-display__price-range">
<div class="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 class="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 class="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>2. Bedah: Grup Filter
Tiap grup dibungkus <details> (bisa dilipat, aksesibel tanpa JavaScript, pola yang sama dengan dropdown di 36 Navigasi dan Menu > Dropdown yang Aksesibel). <summary>-nya nampilin filter.label dan jumlah nilai aktif dalam kurung. Di dalamnya ada link Reset ke filter.url_to_remove kalau ada yang aktif.
3. Bedah: Tiga Tipe Filter
Ini inti form-nya, case filter.type:
boolean: dua checkbox, satu buattrue_value, satu buatfalse_value. Nama input pakaifilter.param_name.list: loopfilter.values, tiap nilai jadi checkbox denganname="{{ filter_value.param_name }}"danvalue="{{ filter_value.value }}". Karena nama dan nilainya diambil dari object, form ini otomatis ngasilin URL parameter yang bener (filter.v.option.color=red) tanpa kamu nyusun string sendiri.price_range: dua input angka,min_valuedanmax_value, denganmaxdarifilter.range_max. Nilainya dilewatinmoney_without_currency | replace: ',', ''biar jadi angka polos tanpa pemisah ribuan.
Tiga hal yang berlaku di semua checkbox:
checkedkalauactive, biar state dari URL tercermin.disabledkalaucount == 0dan gak aktif, biar pembeli gak milih nilai yang hasilnya kosong. Ini prinsip “hindari jalan buntu” di bawah.idunik buat pasanganlabel/input.
❌ nilai dengan nol hasil tetap bisa dipilih, pembeli mendarat di halaman kosong
<input type="checkbox" name="{{ filter_value.param_name }}" value="{{ filter_value.value }}">✅ dinonaktifin
<input type="checkbox" name="{{ filter_value.param_name }}" value="{{ filter_value.value }}"
{% if filter_value.count == 0 and filter_value.active == false %}disabled{% endif %}>4. Bedah: Swatch Warna dan Gambar
filter_value.display (object filter_value_display dari 17 Objects Lainnya > 3. filter_value_display) ngasih tampilan visual buat nilai filter, yang merchant atur di Search & Discovery:
colors: array warna. Contoh di atas ngerakitconic-gradientbiar satu lingkaran bisa nampilin sampai 4 warna (dibagi rata, diputar 45° kalau cuma dua biar kelihatan diagonal). Ini cara nampilin swatch “merah/putih” tanpa gambar.image: object gambar, dirender pakaiimage_url+image_tagukuran 25px.- Selain itu: placeholder kosong.
5. Bedah: Filter Aktif
Bagian bawah form nampilin chip filter aktif yang bisa dihapus satu-satu, plus Clear all:
- Clear all nunjuk ke
collection.urldengansort_bydipertahankan, biar ngehapus filter gak ngereset urutan. - Price range: tampil kalau
min_value.valueataumax_value.valueada, dengan default0danrange_max, dihapus lewatfilter.url_to_remove. - Lainnya: loop
filter.active_values, tiap chip nunjuk kefilter_value.url_to_removedan nampilinlabelgrup pluslabelnilai.
6. Form Filter Hasil Pencarian
Hampir identik, dengan tiga perbedaan:
- Sumbernya
search.filters, bukancollection.filters. - Ada input tersembunyi
qberisisearch.termsbiar kata kuncinya ikut waktu form disubmit. - Clear all nunjuk ke
routes.search_urldengansort_bydanq.
<form>
<input type="hidden" name="q" value="{{ search.terms }}" />
{%- for filter in search.filters -%}
...
{%- endfor -%}
<div>
<p><a href="{{ routes.search_url }}?sort_by={{ search.sort_by }}&q={{ search.terms }}">Clear all</a></p>
...
</div>
</form>Catatan: kalau filter diterapin di hasil pencarian, semua hasil non-produk (artikel, halaman) dibuang. Detail pencariannya di 38 Search dan Predictive Search.
Sorting
Filter dan sort selalu berdampingan. Urutannya lewat parameter sort_by di URL, dan collection.sort_options (juga search.sort_options) ngasih daftar opsi dengan name dan value, dibahas di 14 Objects Produk dan Koleksi dan 17 Objects Lainnya > sort_option:
<select name="sort_by" id="SortBy">
{% for option in collection.sort_options %}
<option value="{{ option.value }}"{% if option.value == collection.sort_by %} selected{% endif %}>
{{ option.name }}
</option>
{% endfor %}
</select>Taruh di form yang sama dengan filter biar satu submit ngirim dua-duanya, dan selalu pertahankan sort_by di link Clear all.
Pedoman UX Filtering
Filtering itu langsung ngaruh ke konversi, jadi Shopify ngasih pedoman yang jelas:
1. Antarmuka yang Tepat per Tipe
list: checkbox, biar jelas bisa milih banyak sekaligus.price_range: slider dua pegangan, tapi tetap sediain input angka “From” dan “To” buat aksesibilitas, dengan label (boleh disembunyiin secara visual).
2. Pilihan Layout
Dua layout standar di layar besar, dan sebaiknya dua-duanya disediain di balik theme setting:
- Toolbar horizontal di atas grid: kompak, gabungin filter dan sort, cocok buat toko kecil dengan kurang dari lima filter.
- Sidebar vertikal di kiri grid: cocok buat toko dengan lebih dari lima filter.
3. Hierarki Visual yang Jelas
Nama grup filter harus menonjol dari nilainya, dan antar grup harus ada pemisah yang jelas.
4. Hindari Jalan Buntu
Pembeli gak boleh berakhir dengan nol hasil. Selalu tampilin jumlah hasil (count) di tiap nilai, dan nonaktifin nilai yang hasilnya nol.
5. Tampilin Filter yang Aktif
Kalau pembeli mendarat di halaman yang udah ada filternya (dari link yang dibagiin), mereka harus lihat filter apa yang aktif. Kalau nambahin filter, harus ada umpan balik. Dan harus gampang ngehapus satu filter atau semua.
Biasanya label nilai udah cukup jelas (“Sepatu”, “Tersedia”). Tapi kadang gak, misalnya ada filter “Lebar” dan “Tinggi” yang nilainya sama-sama angka. Jadi sediain theme setting buat nyertain nama grup di chip filter aktif (“Lebar: 30”).
6. Kelola Grup Filter
Di sidebar vertikal, lipat grup setelah lima pertama, biar gampang dipindai tanpa scroll panjang.
7. Kelola Nilai Filter
Satu grup bisa punya sampai 100 nilai. Potong jadi 10 dengan tombol “Show more”. Dan kalau bisa, tampilin 10 yang paling relevan, bukan 10 pertama.
8. Mobile
Pindahin antarmuka filter ke drawer atau modal. Dua cara nampilinnya:
- Satu tombol “Filter” yang buka drawer.
- Daftar grup filter yang scroll horizontal, tiap grup buka modal filternya sendiri, plus tombol “Filter” sebagai cadangan.
9. Progressive Enhancement
Bangun sebanyak mungkin tanpa JavaScript: link atau form dengan tombol submit manual. JavaScript ditambahin di atasnya buat update grid secara live. Kalau JavaScript-nya gagal dimuat, filter tetap jalan. Prinsip ini dibahas lagi di 61 Performance JavaScript dan INP.
Tag Filtering (Cara Lama)
Shopify sendiri bilang: pertimbangkan storefront filtering daripada ini. Tapi theme lama masih banyak yang makainya.
1. Cara Kerja
Filter tag diterapin dengan nambahin /[tag-handle] ke URL koleksi, handle-nya versi handleize dari tag:
{shop}.myshopify.com/collections/frontpage/newBanyak tag digabung pakai +, logikanya AND:
{shop}.myshopify.com/collections/frontpage/new+saleKalau gak ada produk yang punya kedua tag, pembeli lihat halaman kosong, jalan buntu yang persis dilarang pedoman UX di atas.
2. Redirect Tag yang Gak Dipakai
Kalau tag di URL gak dipakai produk manapun di toko, Shopify redirect ke URL tanpa tag itu. Promo summer-sale selesai dan tag-nya dihapus dari semua produk? Link lama /collections/frontpage/summer-sale otomatis ke /collections/frontpage. Kalau ada banyak tag, cuma yang gak berlaku yang dibuang.
3. Implementasi
Loop collection.all_tags, cek ada di current_tags atau gak, dan output link tambah/hapus pakai filter link_to_add_tag dan link_to_remove_tag dari 19 Filters HTML, URL, Media, Color, dan Font > 3. Filter Tag Filtering:
{% if collection.all_tags.size > 0 %}
<ul class="tag-filters">
{% for tag in collection.all_tags %}
{% if current_tags contains tag %}
<li class="tag-filters__item active">{{ tag | link_to_remove_tag: tag }}</li>
{% else %}
<li class="tag-filters__item">{{ tag | link_to_add_tag: tag }}</li>
{% endif %}
{% endfor %}
</ul>
{% endif %}Kenapa storefront filtering lebih baik: merchant gak perlu ngerawat sistem tag manual, filternya bisa dari harga/ketersediaan/opsi varian yang gak mungkin jadi tag, ada count buat nyegah jalan buntu, dan berlaku juga di hasil pencarian.
Ringkasan
- Dua cara filter: storefront filtering (disarankan, dari Search & Discovery, koleksi dan pencarian) dan tag filtering (lama, koleksi aja).
- Sumber filter: availability, category, price, tag, type, vendor, variant option, metafield. AND antar filter, OR antar nilai. Maksimal 25 filter, gak tampil kalau koleksi > 5.000 produk atau pencarian > 1.000.
- URL parameter:
filter.p.*level produk (tag,product_type,vendor,t.category,m.ns.key),filter.v.*level varian (availability,option.nama,price.lte/gte,t.shopify.key,m.ns.key). Banyak nilai pakai koma. - Filter varian aktif ngubah
product.featured_mediadanproduct.urlke varian pertama yang cocok. - Form filter: loop
collection.filters/search.filters,case filter.typebuatboolean(true/false value),list(checkbox darifilter.valuesdenganparam_name/valuedari object),price_range(input min/max denganrange_max). - Selalu:
checkedkalauactive,disabledkalaucount == 0, swatch darifilter_value.display(colors → conic-gradient, image → image_tag). - Filter aktif sebagai chip dengan
url_to_remove, plus Clear all yang mempertahankansort_by(danqdi pencarian). - Pencarian: input tersembunyi
q, dan filter aktif ngebuang hasil non-produk. - UX: checkbox buat list, slider + input angka buat harga, layout toolbar/sidebar sebagai setting, hierarki jelas, tanpa jalan buntu, filter aktif kelihatan dan bisa dihapus, lipat grup setelah 5, potong nilai ke 10, drawer di mobile, progressive enhancement.
- Tag filtering:
/collections/x/tag1+tag2(AND), redirect otomatis buat tag yang gak dipakai, implementasi lewatall_tags,current_tags,link_to_add_tag/link_to_remove_tag.
Bab selanjutnya kita bahas pencarian: form search dan parameter URL-nya, object search, cara nampilin hasil campuran (produk, artikel, halaman), dan predictive search yang nampilin saran waktu pembeli lagi ngetik lewat Ajax API.