Menu di 36 Navigasi dan Menu buat yang mau jelajah, filter di 37 Filtering buat yang mau nyempitin. Pencarian buat yang udah tau mau cari apa dan gak mau klik-klik dulu.

Ada dua lapisan pencarian di theme Shopify:

  1. Storefront search, pencarian “beneran” yang ngebawa pembeli ke halaman /search berisi hasil lengkap. Dikendaliin lewat query parameter di URL.
  2. Predictive search, saran yang muncul sambil ngetik di kotak pencarian, tanpa pindah halaman. Dikendaliin lewat Predictive Search API (Ajax).

Dua-duanya bisa disetel merchant lewat app Search & Discovery (Search Settings), yang bisa ngubah nilai default beberapa parameter di bawah.

1. Query Parameter

Pencarian nerima parameter ini di URL:

  1. q (wajib), kata kuncinya.
  2. type, tipe hasil yang diminta, dipisah koma: product, page, article. Default semua tipe (bisa diubah di Search Settings).
  3. page, halaman hasil ke berapa, default 1.
  4. options[unavailable_products], produk/varian yang gak tersedia mau diapain: show (tampil sesuai urutan ditemukan), hide (dibuang), last (ditaruh paling akhir). Default last.
  5. options[prefix], pencocokan sebagian kata di term terakhir: last (default, “winter snow” nemuin “snow”, “snowshoe”, “snowboard”) atau none.
  6. sort_by: relevance (default), price-ascending, price-descending. Buat dua yang terakhir, semua hasil non-produk didorong ke akhir.

Kenapa prefix penting: tanpa itu, pembeli yang ngetik “sepat” gak nemu “sepatu”. Dan kenapa cuma term terakhir? Karena term sebelumnya dianggap udah selesai diketik.

2. Form Pencarian

Form-nya elemen <form> dengan action ke halaman search. Selalu pakai routes.search_url, jangan hardcode /search, biar toko multi bahasa dapat URL yang bener (/fr/search). Tiap parameter jadi input dengan name parameter dan value-nya. Selain q, semua parameter gak butuh input pembeli, jadi jadiin hidden input:

<form action="{{ routes.search_url }}">
  <input type="text"
    placeholder="Search"
    name="q"
    value="{{ search.terms | escape }}"
  />
  <input type="hidden" name="type" value="product,page" />
  <input type="hidden" name="options[unavailable_products]" value="hide" />
  <input type="hidden" name="options[prefix]" value="last" />
  <input type="submit" value="Search" />
</form>

Dua detail:

  1. value="{{ search.terms | escape }}", biar kata yang dicari tetap ada di kotak setelah pencarian (pedoman UX di bawah), dan escape biar tanda kutip di kata kunci gak ngerusak HTML.
  2. Nilai hidden input bisa dijadiin theme setting (misalnya checkbox “Sembunyiin produk habis dari hasil pencarian”) biar merchant yang nentuin.

❌ hardcode, rusak di toko multi bahasa

<form action="/search">

✅ dari routes

<form action="{{ routes.search_url }}">

3. Struktur URL

Waktu form disubmit, URL halaman search mencerminkan parameternya. Pencarian snow dengan type=product,page, unavailable_products=hide, prefix=last:

/search?q=snow&type=product,page&options[unavailable_products]=hide&options[prefix]=last

Ini yang bikin hasil pencarian bisa dibagiin dan tombol back jalan, prinsip yang sama dengan URL filter.

4. Halaman Hasil

Di template search, object search dari 17 Objects Lainnya > search nyediain performed, terms, results (campuran produk/artikel/halaman dengan object_type), results_count, types, filters, dan sort_by/sort_options. Pola dasarnya udah ada di sana, yang perlu diingat:

  1. Cek search.performed dulu. Kalau false (buka /search tanpa q), tampilin form-nya aja.
  2. Wajib paginate search.results, maksimal 50 per halaman.
  3. case item.object_type buat ngerender kartu yang beda per tipe.
  4. Filter highlight dari 19 Filters HTML, URL, Media, Color, dan Font > Filter Pencarian buat nyorot kata kunci di deskripsi.
  5. Form filter hasil pencarian (dengan hidden input q) dari 37 Filtering > 6. Form Filter Hasil Pencarian, ingat filter aktif ngebuang hasil non-produk.

Implementasi lengkapnya bisa dilihat di main-search Dawn.

Saran muncul langsung waktu ngetik: produk, koleksi, query (saran kata kunci), halaman, dan artikel. Manfaatnya: pembeli bisa merumuskan dan memperhalus pencariannya, nemuin cara baru buat jelajah toko, dan langsung lihat kecocokan tanpa ninggalin halaman.

1. Gimana Saran Dibikin

Begitu pembeli ngetik, predictive search nyari yang cocok, persis atau dengan toleransi typo, di properti yang bisa dicari. Beberapa aturan:

  1. Kata yang dipisah tanda hubung atau spasi dianggap dua term. T-shirt dan t shirt ngasih hasil sama, tshirt beda.
  2. Ketik very-fast snowbo → produk yang mengandung very, fast, dan term yang diawali snowbo.
  3. Varian cuma dikembaliin kalau query cocok sama term spesifik judul varian, dan cuma varian dengan term cocok terbanyak. Cari snowbo → produk snowboard. Cari light blue snowbo → cuma varian light blue. Waktu varian yang cocok, featured_media/image dan url produk ngikutin varian itu.
  4. Query suggestion (saran kata kunci) dihasilin dengan ngekstrak kata dan frasa dari katalog produk dan pencarian pembeli pakai NLP. Tapi cuma bahasa Inggris, dan butuh locale toko dan locale pembeli sama-sama Inggris.

2. Tiga Komponen Implementasi

  1. Main search, input pencarian tempat predictive search nempel.
  2. Section predictive search, section yang ngerender saran dari predictive_search.resources.
  3. Fungsi JavaScript, yang ngambil section itu lewat section response Predictive Search API dan nyuntikinnya ke halaman.

Ini pola yang sama dengan product recommendations di 41 Produk Recommendations, Gift Card, dan Bundles: object Liquid-nya cuma terisi kalau section dirender lewat API, jadi kamu butuh JavaScript buat “manggil” section-nya. Contoh di sini ngikutin pola listbox dari W3C ARIA Authoring Practices. Implementasi lengkapnya di Dawn: sections/main-search.liquid, sections/predictive-search.liquid, assets/predictive-search.js.

<script src="{{ 'predictive-search.js' | asset_url }}" defer="defer"></script>
<predictive-search>
  <form action="{{ routes.search_url }}" method="get" role="search">
    <label for="Search">Search</label>
    <input
      id="Search"
      type="search"
      name="q"
      value="{{ search.terms | escape }}"
      role="combobox"
      aria-expanded="false"
      aria-owns="predictive-search-results"
      aria-controls="predictive-search-results"
      aria-haspopup="listbox"
      aria-autocomplete="list"
     />
    <input name="options[prefix]" type="hidden" value="last" />
    <div id="predictive-search" tabindex="-1"></div>
  </form>
</predictive-search>

Yang penting:

  1. Dibungkus custom element <predictive-search> yang dibikin di JavaScript (web component, pola yang sama dengan 35 Developer Preview tag {% block %} dan {% partial %}).
  2. Form-nya tetap form biasa ke routes.search_url. Kalau JavaScript gagal, Enter tetap ngebawa ke halaman hasil. Progressive enhancement.
  3. Atribut ARIA role="combobox", aria-expanded, aria-controls, aria-haspopup="listbox", aria-autocomplete="list" nandain ke screen reader kalau input ini punya daftar saran.
  4. <div id="predictive-search"> itu wadah kosong yang bakal diisi hasil section.

Ngerender predictive_search.resources dari 17 Objects Lainnya > predictive_search. Contoh ini cuma produk, Dawn ngerender semua tipe:

{%- if predictive_search.performed -%}
  <div id="predictive-search-results">
    {%- if predictive_search.resources.products.size > 0 -%}
      <h3 id="predictive-search-products">
        Products
      </h3>
      <ul role="listbox" aria-labelledby="predictive-search-products">
        {%- for product in predictive_search.resources.products -%}
          <li role="option">
            <a href="{{ product.url }}">
              {{ product | image_url: width: 50 | image_tag }}
              <span>{{ product.title }}</span>
            </a>
          </li>
        {%- endfor -%}
      </ul>
    {%- endif -%}
    <button>
      Search for “{{ predictive_search.terms }}”
    </button>
  </div>
{%- endif -%}

predictive_search.performed false waktu section dirender normal (misalnya kalau kamu iseng naruh section ini di template), jadi gak ngeluarin apa-apa. Cuma true waktu dirender lewat API. Tombol “Search for …” di bawah itu link “lihat semua hasil” yang diwajibin pedoman UX.

5. Fungsi JavaScript

class PredictiveSearch extends HTMLElement {
  constructor() {
    super();
    this.input = this.querySelector('input[type="search"]');
    this.predictiveSearchResults = this.querySelector('#predictive-search');
    this.input.addEventListener('input', this.debounce((event) => {
      this.onChange(event);
    }, 300).bind(this));
  }
  onChange() {
    const searchTerm = this.input.value.trim();
    if (!searchTerm.length) {
      this.close();
      return;
    }
    this.getSearchResults(searchTerm);
  }
  getSearchResults(searchTerm) {
    fetch(`/search/suggest?q=${searchTerm}&section_id=predictive-search`)
      .then((response) => {
        if (!response.ok) {
          var error = new Error(response.status);
          this.close();
          throw error;
        }
        return response.text();
      })
      .then((text) => {
        const resultsMarkup = new DOMParser().parseFromString(text, 'text/html').querySelector('#shopify-section-predictive-search').innerHTML;
        this.predictiveSearchResults.innerHTML = resultsMarkup;
        this.open();
      })
      .catch((error) => {
        this.close();
        throw error;
      });
  }
  open() {
    this.predictiveSearchResults.style.display = 'block';
  }
  close() {
    this.predictiveSearchResults.style.display = 'none';
  }
  debounce(fn, wait) {
    let t;
    return (...args) => {
      clearTimeout(t);
      t = setTimeout(() => fn.apply(this, args), wait);
    };
  }
}
customElements.define('predictive-search', PredictiveSearch);

Alurnya:

  1. Debounce 300ms di event input, biar gak nembak API tiap huruf. Ini penting karena API punya throttle (429 kalau kebanyakan).
  2. Kalau kosong, tutup. Kalau ada, fetch ke /search/suggest dengan q dan section_id=predictive-search (nama file section-nya).
  3. Respons berupa HTML section yang udah dirender dengan predictive_search terisi. Parse pakai DOMParser, ambil isi #shopify-section-predictive-search (ID pembungkus section, format shopify-section-<id> dari 26 Sections dan Section Schema), suntik ke wadah.

Contoh ini hardcode /search/suggest. Yang disarankan: pakai routes.predictive_search_url dari Liquid (dilempar ke JavaScript lewat atribut data atau window.Shopify.routes.root) biar URL-nya sadar locale.

❌ hardcode, salah di /fr/

fetch(`/search/suggest?q=${searchTerm}&section_id=predictive-search`)

✅ sadar locale

fetch(`${window.Shopify.routes.root}search/suggest?q=${searchTerm}&section_id=predictive-search`)

Predictive Search API

Detail endpoint-nya, bagian dari Ajax API yang dibahas umum di 51 Ajax API dan Section Rendering API. Semua request harus pakai URL sadar locale.

1. GET /{locale}/search/suggest.json

Ngembaliin JSON mentah. Buat theme yang mau ngerender saran sendiri di JavaScript (jarang, karena section response lebih praktis). Parameternya:

  1. q (wajib).
  2. resources[type]: product, page, article, collection, query, dipisah koma. Default query,product,collection,page (bisa diubah di Search Settings).
  3. resources[limit]: 1 sampai 10, default 10.
  4. resources[limit_scope]: all (default, total limit di semua tipe) atau each (limit per tipe).
  5. resources[options][unavailable_products]: show, hide, last (default), cuma buat product.
  6. resources[options][fields]: field yang dicari: author, body, product_type, tag, title, variants.barcode, variants.sku, variants.title, vendor. Default title, product_type, variants.title, vendor, dan Shopify nyaranin tetap pakai default buat pengalaman terbaik.
fetch(window.Shopify.routes.root + "search/suggest.json?q=bag&resources[type]=product&resources[options][unavailable_products]=hide&resources[options][fields]=title,product_type,variants.title")
  .then((response) => response.json())
  .then((suggestions) => {
    const productSuggestions = suggestions.resources.results.products;
    if (productSuggestions.length > 0) {
      const firstProductSuggestion = productSuggestions[0];
      alert(`The title of the first product suggestion is: ${
        firstProductSuggestion.title}`
      );
    }
  }
);

Responsnya resources.results dengan array queries, products, collections, pages, articles. Object produknya punya available, body, compare_at_price_min/max, handle, id, image, price, price_min/max, tags, title, type, url, variants (dengan featured_image), vendor, featured_image. Query object punya url, text, dan styled_text (bagian yang disarankan udah ditebelin). Perhatiin url produk bawa parameter _pos, _psq, _ss, _v buat analitik pencarian.

Hati-hati: jangan output body di toko multi bahasa, karena isinya gabungan semua terjemahan.

2. GET /{locale}/search/suggest

Ngembaliin HTML section yang dirender dengan predictive_search terisi. Parameter sama dengan .json plus section_id (wajib). Ini yang dipakai contoh JavaScript di atas, dan ini cara yang disarankan karena markup-nya ditulis di Liquid, satu sumber kebenaran buat tampilan.

const predictiveSearchSection = document.querySelector('.predictive-search-results');
var requestResponse;
fetch(window.Shopify.routes.root + "search/suggest?q=bag&resources[type]=product&resources[options][unavailable_products]=hide&resources[options][fields]=title,product_type,variants.title&section_id=predictive-search")
  .then((response) => {
    requestResponse = response;
    return response.text();
   })
  .then((text) => {
    if (!requestResponse.ok) {
      throw new Error(`${requestResponse.status}: ${text}`);
    }
    const resultsMarkup = new DOMParser()
      .parseFromString(text, 'text/html')
      .querySelector('#shopify-section-predictive-search').innerHTML;
    predictiveSearchSection.innerHTML = resultsMarkup;
  })
  .catch((error) => {
    console.error(error);
  });

Section ini:

{%- if predictive_search.performed -%}
  <div id="predictive-search-results">
    {%- if predictive_search.resources.products.size > 0 -%}
      <h3>Products</h3>
      <ul>
        {%- for product in predictive_search.resources.products -%}
          <li><a href="{{ product.url }}">{{ product.title }}</a></li>
        {%- endfor -%}
      </ul>
    {%- endif -%}
  </div>
{%- endif -%}

Ngasilin respons:

<div id="shopify-section-predictive-search">
  <div id="predictive-search-results">
    <h3>Products</h3>
    <ul>
      <li><a href="/products/running-shoes">Running Shoes</a></li>
      <li><a href="/products/tennis-shoes">Tennis Shoes</a></li>
    </ul>
  </div>
</div>

3. Error

  1. 404, section_id gak ketemu di theme.
  2. 417 Expectation Failed, bahasa pembeli gak didukung ("Unsupported buyer locale").
  3. 422 Invalid parameter, nilai parameter salah, description-nya ngejelasin.
  4. 429 Too many requests, kena throttle, ada header Retry-After dalam detik.
  5. Lainnya 5xx.

Makanya contoh JavaScript-nya ngecek response.ok dan nutup dropdown kalau error, jangan sampai pembeli lihat HTML error di dropdown.

4. Properti yang Dicari

  1. Produk: body, product_type, tag, title, variants.barcode, variants.sku, variants.title, vendor.
  2. Halaman: author, body, title.
  3. Artikel: author, body, tag, title.
  4. Koleksi: title aja.

Di storefront yang diterjemahin, yang bisa dicari lebih sedikit: produk body, title, variants.title; halaman dan artikel body, title. Dan saran koleksi cuma berdasarkan bahasa utama toko, gak ngecek terjemahan koleksi.

5. Typo Tolerance dan Partial Match

Typo tolerance = 1: hasil yang beda satu huruf, atau dua huruf tertukar, tetap ketemu. Syaratnya 4 huruf pertama harus bener. Berlaku di produk (title, product_type, variants.title, vendor), halaman dan artikel (author, title), koleksi (title).

Partial word match: sweate → sweater. Batasannya: cuma di term terakhir kalau query lebih dari satu term, cuma di akhir kata (book gak nemuin ebook), dan cuma buat bahasa yang didukung. Predictive search pakai mesin pencari yang beda dari storefront search, jadi partial match-nya beda: storefront search cuma partial match kalau options[prefix]=last.

6. Bahasa yang Didukung

Predictive search jalan kalau locale pembeli salah satu dari: Afrikaans, Albania, Armenia, Bosnia, Bulgaria, Katalan, Kroasia, Ceko, Denmark, Belanda, Inggris, Estonia, Faroe, Finlandia, Prancis, Gaelik, Jerman, Yunani, Hungaria, Islandia, Indonesia, Italia, Latin, Latvia, Lituania, Makedonia, Moldova, Norwegia (Bokmål dan Nynorsk), Polandia, Portugis (Brasil dan Portugal), Rumania, Rusia, Serbia, Serbo-Kroasia, Slovakia, Slovenia, Spanyol, Swedia, Turki, Ukraina, Vietnam, Welsh.

Cara ngecek dari JavaScript: di <head> ada <script id="shopify-features"> berisi JSON dengan kunci predictiveSearch true/false. Cek ini sebelum nyalain fitur, biar pembeli berbahasa Jepang (gak ada di daftar) gak dapat dropdown yang selalu error 417.

7. Batasan Lain

  1. Produk individual gak bisa dikecualiin dari saran, kecuali disembunyiin dari mesin pencari lewat metafield seo.hidden (dibahas di 48 SEO Metadata, hreflang, robots.txt).
  2. Maksimal 10 saran per tipe per request.
  3. Query suggestion cuma Inggris.

1. Persyaratan UI

  1. Jumlah hasil: API default maksimal 10 di semua tipe, jadi atur campurannya (limit_scope=each kalau mau per tipe). Batasi jumlah saran biar gak kebanyakan, dan tiap tipe diberi label atau gaya berbeda.
  2. Tombol tutup (ikon atau teks) yang keluar dari pengalaman pencarian dan nutup dropdown, tanpa ngehapus kata kunci.
  3. Tombol cari yang ngebawa ke halaman hasil, juga bisa lewat Enter atau “go”/“search” di keyboard mobile.
  4. Kata kunci tetap di kotak sampai pembeli ngehapusnya sendiri atau pindah halaman, dan diulang di halaman hasil.
  5. Empty state: sembunyiin dropdown kalau gak ada saran, biar pembeli lanjut ke halaman hasil.
  6. Mobile: fokusin input waktu ikon search diketuk, dan jangan ganggu scroll di dalam dropdown.
  7. Desktop: jangan sembunyiin info di balik scroll atau tinggi tetap, biarin dropdown nyesuain tinggi kontennya.

2. Aksesibilitas

Matiin autocomplete browser, riwayat pencarian, dan autocorrect/autocomplete OS mobile (autocomplete="off", autocorrect="off", autocapitalize="off", spellcheck="false"), karena overlay-nya bertabrakan sama dropdown kamu dan logika autocomplete-nya bersaing.

3. Praktik Terbaik

  1. Info seminimal mungkin yang cukup buat milih: query → styled_text; produk → gambar, judul, harga; koleksi, artikel, halaman → judul aja.
  2. Query suggestion: sorot bagian yang disarankan, bukan yang diketik. Pembeli ngetik “card”, saran “cardigan” ditampilin sebagai cardigan, bukan cardigan. Lebih gampang dipindai.
  3. Label: “Sold out” cuma kalau seluruh produk gak tersedia. Pakai heading (“Products”) biar pembeli tau tipe hasilnya.
  4. Tombol clear di kotak pencarian.
  5. Visibilitas: kotak atau minimal ikon search di header semua halaman. Toko dengan sedikit produk boleh naruhnya di menu hamburger.
  6. Gelapin latar halaman waktu dropdown aktif biar fokus.
  7. Link “more results”: “Search for [query]” atau “View all results” di daftar saran, karena hasil lengkapnya mungkin lebih banyak.

4. Pola Layout

  1. Inline search (desktop dan mobile): kotak pencarian kelihatan di header, dropdown muncul di bawahnya dan menimpa halaman.
  2. In-menu search: gak ada kotak di header, pencarian ada di dalam menu, saran muncul di bawah kotak di dalam menu (atau kotak di atas menu dengan saran menimpa daftar menu).

Ringkasan

  1. Storefront search dikendaliin query parameter: q, type (product/page/article), page, options[unavailable_products] (show/hide/last), options[prefix] (last/none), sort_by (relevance/price-ascending/price-descending).
  2. Form pencarian: action="{{ routes.search_url }}", input q dengan value="{{ search.terms | escape }}", parameter lain sebagai hidden input.
  3. Halaman hasil: cek search.performed, paginate maksimal 50, case object_type, highlight, form filter dengan hidden q.
  4. Predictive search = main search (form biasa + ARIA combobox + wadah kosong) + section yang ngerender predictive_search.resources (cuma terisi lewat API) + JavaScript yang fetch /search/suggest?section_id=... dengan debounce dan nyuntik #shopify-section-<id>.
  5. Dua endpoint: suggest.json (JSON mentah) dan suggest (HTML section, disarankan), parameter resources[type], resources[limit] (1–10), resources[limit_scope] (all/each), resources[options][unavailable_products], resources[options][fields]. Selalu URL sadar locale.
  6. Error: 404 section, 417 bahasa, 422 parameter, 429 throttle (Retry-After).
  7. Pencocokan: typo tolerance 1 (4 huruf pertama bener), partial match cuma term terakhir dan akhir kata, varian cuma kalau judul varian cocok, T-shirt = t shirt ≠ tshirt.
  8. Bahasa didukung termasuk Indonesia, cek predictiveSearch di #shopify-features. Query suggestion cuma Inggris, koleksi cuma bahasa utama, maksimal 10 per tipe, produk cuma bisa disembunyiin lewat seo.hidden.
  9. UX: tombol tutup dan cari, kata kunci dipertahankan, sembunyiin empty state, matiin autocomplete browser, info minimal, sorot bagian saran, link “search for”, gelapin latar.

Bab selanjutnya kita masuk ke bagian produk: cara nampilin dan milih varian (dropdown, swatch, radio), update harga dan ketersediaan waktu varian berubah, deep link varian, dan penanganan produk dengan varian sangat banyak (sampai 2.048).