← All articles
Guide · July 2026

Building a category page with Browse & Discovery

A category page has no query to process, just products to rank well. This guide indexes a small catalog with real categories, then renders a dynamically sorted page using the provided snippet.

What you need

An API key (14-day free trial, no credit card) and an HTML page to inject results into. No SDK to install.

Step 1 — Index with categories

categories (or category, singular) is a field you supply, exactly like name or stock: reuse the taxonomy you already have on your site, no need to redefine it for Heurix. Add price too if you want to sort by price:

curl -X POST https://api.heurix.fr/v1/index/mysite/items   -H "Authorization: Bearer YOUR_API_KEY"   -H "Content-Type: application/json"   -d '{
  "items": [
    {"id": "sku-1", "name": "BOSCH GDX 18V-285 Drill",
     "categories": ["power-tools", "drills-drivers"],
     "brand": "Bosch", "price": 139.90, "stock": 8},
    {"id": "sku-2", "name": "MAKITA HP457D Drill",
     "categories": ["power-tools", "drills-drivers"],
     "brand": "Makita", "price": 99.90, "stock": 3}
  ]
}'

Two levels in categories: the main category AND the subcategory, in the same list. A product indexed this way shows up both on a "Power tools" page and on a more specific "Drills & drivers" page — every value in the list is a potential category page.

Step 2 — Check what was detected

Before writing any code on the site side, confirm what Heurix understood from your catalog:

curl https://api.heurix.fr/v1/index/mysite/browse-categories   -H "Authorization: Bearer YOUR_API_KEY"
{"categories": [
  {"category": "power-tools", "products": 2},
  {"category": "drills-drivers", "products": 2}
]}

These same categories are also visible and manageable from your console, under Dashboard → Browse & Discovery.

Step 3 — Render the page, with the provided snippet

Download heurix-browse-widget.js, fill in your key and catalog at the top of the file, then include it on your category page:

<div id="my-category-page"></div>
<script src="heurix-browse-widget.js"></script>
<script>
  Heurix.browse({
    catalog: "mysite",
    category: "drills-drivers",
    sort: "price_asc",
    containerId: "my-category-page"
  });
</script>

By default, each product renders inside a plain <div class="heurix-product">, to style with your own CSS. For full control over the generated HTML (card grid, buttons, promo badges…), provide your own render function:

Heurix.browse({
  catalog: "mysite",
  category: "drills-drivers",
  containerId: "my-category-page",
  renderItem: function (hit) {
    return '<div class="my-product-card">' +
      '<h3>' + hit.product.name + '</h3>' +
      '<span>' + hit.product.price + ' €</span>' +
      (hit.in_stock ? '' : '<span class="out-of-stock">Out of stock</span>') +
    '</div>';
  }
});

Going further: sort, filter, feature

A sort selector wires up by simply calling Heurix.browse() again with a different sort on value change: stock, recent, alphabetical, price_asc/price_desc, margin, or popular (real clicks and purchases, if you've set up the conversion tracker).

To narrow within a category (by brand, by color…), pass a filters object: any non-reserved product field automatically becomes filterable, with no configuration:

Heurix.browse({
  catalog: "mysite", category: "drills-drivers",
  filters: { brand: "Makita" },
  containerId: "my-category-page"
});

When the API does not answer

Since September 2026, a call that drags on is given up after 5 seconds — a shopper is waiting in front of the page, better to hand control back. Adjust it with timeoutMs; 0 disables the timeout.

An outage no longer shows up as an empty category: the message "No products in this category" is reserved for the case where the engine did answer and found nothing. The detailed diagnosis — rejected key, unauthorised domain, catalog not found, quota — goes to the browser console, where a developer looks, never in front of a shopper.

The promise returned by Heurix.browse() still resolves exactly as before: a .then() written without a .catch() does not start throwing. On failure it resolves with { total: 0, hits: [], heurixError: { code, transitoire } } — hits is always an array, which was not the case before.

And to feature an entire brand or product line without editing product by product, an attribute boost rule is set once from the console (or the API). See the full documentation for the pinning and boosting endpoint details.

14-day free trial

Try Heurix on your catalog, no credit card.

Start free trial