---
title: Catalog & Search
description: Listing and looking up products, and live typeahead search over WebSocket.
---

## Products

`storefront.products` — list/filter the catalog and look up individual products.

```js
storefront.products.get({ q: 'shoes', page: 2 }, function(err, data) {
  if (err) return console.error(err)
  console.log(data.products, data.total_pages)
})

storefront.products.getForCart('aurora-runner', function(err, product) {
  if (err) return console.error(err)
  // product.variant_options / product.variant_list — the color/size matrix,
  // same shape product-variant-selector (see Components) expects
})
```

- **`get(params?, fn)`** — List products, optionally filtered (`q`, `collections`,
  `product_types`, `vendors`, `tags`, `price_gte`, `price_lte`, `page`, `facets`). Calls
  `fn(err, data)` with `data: { products, page, per_page, total_items, total_pages, facets }`.
  Each product is in *basic* mode — no variant data.
- **`related(productSlug, fn)`** — Products related to the given one (by tag or collection),
  for cross-sell widgets. Items already in the cart are excluded automatically. Calls
  `fn(err, products)`.
- **`getForCart(productSlug, fn)`** — A single product in cart-ready shape: the full
  `variant_options`/`variant_list` matrix (for multi-axis variant selection), `bundle_config`,
  `modifiers`, `subscription_fees`. This is the only way to get variant data headlessly —
  `get()`'s list endpoint never includes it, even in full mode. Calls `fn(err, product)`.

## Search

`storefront.search` gives you live, typeahead-style product search over a WebSocket
connection to Bolder's hosted search service — the same backend that powers hosted
themes' search boxes.

### bindTo(input, opts?) — the common case

Wires an `<input>` element up end to end: connects on focus (so the connection is warm
before the first keystroke), debounces input, and emits results. Returns an
[Emitter](/frontend/sdk/core#emitter) — call `.destroy()` to unbind.

```js
const handle = storefront.search.bindTo(document.querySelector('#search-input'), {
  minChars: 3,   // default: 3
  debounce: 150, // ms, default: 150
})

handle.on('results', (evt, results, query) => {
  renderResults(results.products, results.groups)
})

handle.on('cleared', () => clearResults())
handle.on('error', (evt, err) => console.error('Search failed', err))

// later, e.g. on component unmount:
handle.destroy()
```

You don't need to manage the connection yourself — `bindTo` reuses one socket per
`storefront` instance and lets it disconnect on inactivity automatically.

### search(query, opts?, fn) — build your own UI

Lower-level: runs a single query without binding to an input. Auto-connects if needed.

```js
storefront.search.search('running shoes', { minChars: 3 }, (err, results) => {
  if (err) return console.error(err)
  console.log(results.products, results.groups)
})
```

### connect(fn?) — pre-warm the connection

Rarely needed directly — both `search()` and `bindTo()` connect automatically. Useful if
you want to open the connection earlier than the first query (e.g. as soon as a search
UI becomes visible, before the user has typed anything) to shave connection latency off
the first result.

```js
storefront.search.connect((err, socket) => {
  if (err) return console.error('Could not connect to search', err)
})
```

### Results shape

```js
{
  products: [
    { id, name, slug, url, type, vendor, price, image, vars, skus }
  ],
  groups: [
    { type, name, slug } // Collection, ProductType, Vendor, ProductSet, or Tag matches
  ]
}
```

Which fields are present depends on config passed to `createStorefront()` — `image` and
`price` require `withImages`/`withPrices` (both on by default for headless).
