Control facet calculations

Elevate, May 20-22-2025, Miami Beach, Florida

Learn about the Types, configuration, and API implementation of Product facets.

After completing this page, you should be able to:

  • Define the purpose of facets in product search and explain how they provide aggregated data alongside primary search results.
  • Differentiate between Distinct, Range, and Count facets by identifying their specific use cases and essential configuration parameters (such as field, scope, level, and ranges).
  • Apply knowledge of facets to construct a ProductSearchRequest JSON for specified facet calculations and interpret the aggregated results from the ProductPagedSearchResponse.
Let's build on our understanding of the Platform Search Query Language. We know how to ask specific questions using compound and simple expressions. Now, let's explore how to get summaries and statistical information about our product data alongside the search results. This is where facets come in—they are essential for building interactive filtering experiences (faceted navigation) for your users.
Facets provide aggregated counts over your search results. They do not change which products are returned by your main query, but they give you extra metadata about the characteristics of those products.
You request facets by adding a facets array to your main ProductSearchRequest body. Each element in this array defines one facet calculation for the API to perform.
// Structure of a request with facets
{
  "query": {
    /* Your main search query here */
  },
  "sort": [
    /* Optional sorting */
  ],
  "limit": 10,
  "offset": 0,
  "facets": [
    // Array to define desired facets
    {
      /* Facet Definition 1 */
    },
    {
      /* Facet Definition 2 */
    }
    // ... more facets
  ]
}
The results of these calculations appear in the facets field of the ProductPagedSearchResponse.

There are three main types of facets: distinct, ranges and count facets.

Distinct facets

  • Purpose: Distinct facets count how many products (or variants) have specific, distinct values for a given field. This is useful for things like colors, brands, sizes (if they are discrete values), or categories.
  • How it works: You specify a field (such as variants.attributes.color.key or categories.id). The API groups the products by the unique values found in that field and counts how many fall into each group (bucket).
  • Key configuration:
    • name: A mandatory unique name you give this facet (for example, "colorFacet") to identify it in the results.
    • scope: Set this parameter to all if the facet must consider all indexed products, not only the products resulting from the search query, which is the default behavior.
    • field (mandatory): The searchable product field to facet on.
    • filter: Additional filtering expression to apply to the facet result before calculating the facet.
    • level: Specify whether to count products (products) or product variants (variants).
    • fieldType: Required if field is a custom attribute.
    • limit: Maximum number of distinct value buckets to return. This improves query performance and is helpful when your UI does not need to display a large number of values. For example, only showing 10 different colors instead of 50 or more.
    • sort: How to sort the buckets, by key or count, asc or desc. For example, sort color by count ascending.
    • includes: Only return buckets for these specific keys. For example, you only want colors red, blue, and green.
    • missing: A key to use for products that do not have the specified field. For example, some products do not have a color value; you can put these in a separate bucket like "misc" or "other colors".

Let’s consider how these concepts might map to a UI:

Facets on a graphic user interface.

Example request

Let's count products by their categories attribute for results that match products with "white" or "White" in their name in this example request:
{
  "query": {
        "fullText": {
          "field": "name",
          "language": "en",
          "value": "white",
          "caseInsensitive": true
        }
  },
  "facets": [
    {
      "distinct": {
        "name": "categoriesFacet",
        "field": "categories",
        "limit": 15,
        "missing": "No Category"
      }
    }
  ]
}
In the response, you will notice that we received the category ID’s and count. The categories are in a flat structure. Some layered navigation UI’s require to display hierarchical categories. To enable rendering the facet values and aggregations in a hierarchical view, you will need to persist the hierarchy in your application, in a shared cache. This should be safe to do because categories and their structure don’t change frequently. You can ready up more on caching strategies and categories in our Category queries best practices module.

Example response

{
  "total": 244,
  "offset": 0,
  "limit": 20,
  "facets": [
    {
      "name": "categoriesFacet",
      "buckets": [
        {
          "key": "52e575d7-16ce-4186-ae67-39bec921e9ca",
          "count": 65
        },
        {
          "key": "2d1c1b7c-6e92-43c5-aa64-c10678793da5",
          "count": 59
        },
        {
          "key": "c2da8bae-daea-40e1-9e94-26753414e8d5",
          "count": 49
        },
        {
          "key": "06733ee5-0323-41e8-871d-bf58d5b6d093",
          "count": 34
        }
      ]
    }
  ],
  "results": [...]
}

Let’s take a look at how a standard product localized attribute facet query and response looks. We must specify the facet by their key. Because of this, you should consider how you get your application to store these keys, to ensure that you can quickly build the request.

{
  "query": {
        "fullText": {
          "field": "name",
          "language": "en",
          "value": "white",
          "caseInsensitive": true
        }
  },
  "facets": [
    {
      "distinct": {
        "name": "size",
        "field": "variants.attributes.size.key",
        "fieldType": "enum",
        "limit": 15,
        "missing": "No value"
      }
    }
  ]
}

In the response we have the attribute value keys.

{
  "total": 244,
  "offset": 0,
  "limit": 20,
  "facets": [
    {
      "name": "sizes", // Matches the name in the request
      "buckets": [ // The results: distinct values and their counts
        { "key": "L", "count": 150 },
        { "key": "M", "count": 125 },
        { "key": "S", "count": 90 },
        { "key": "XL", "count": 70 },
        { "key": "Not Specified", "count": 20 }
      ]
    }
  ],
  "results": [...]
}

Ranges facets

  • Purpose: Ranges facets count how many products fall into predefined numeric or date ranges. Ideal for price brackets, screen sizes, ratings, or date ranges.
  • How it works: You define the boundaries (from, to) for each range (bucket). The API counts how many products have a value in the specified field that falls within each range.
  • Key configuration:
    • name (mandatory): Your unique name for this facet result.
    • scope: Whether the facet must consider only the products resulting from the search (query) or all indexed products (all).
    • field (mandatory): The numeric or date field to facet on (for example, variants.prices.centAmount).
    • level: Specify whether to count products (products) or product variants (variants).
    • fieldType: Required for custom product attributes.
    • ranges (mandatory): An array defining each bucket:
      • key: (Optional) A custom label for the bucket, for example, 0-50 or 51-100. If omitted, a default like *-5000 or 5000-10000 is generated by the API.
      • from: The lower bound (inclusive). Omit for "less than".
      • to: The upper bound (exclusive). Omit for "greater than or equal to".

Example request

Let's count products by price ranges (in cents).

{
  "query": {},
  "facets": [
    {
      "ranges": {
        "name": "priceRanges",
        "field": "variants.prices.centAmount", // Field must be numeric/date
        "ranges": [
          { "key": "under-50", "to": 5000 }, // .. up to 4999 cents
          { "key": "50-to-100", "from": 5000, "to": 10000 }, // 5000 to 9999 cents
          { "key": "100-plus", "from": 10000 } // 10000 cents and above
        ]
      }
    }
  ]
}

Example response

{
  // ... other response fields
  "facets": [
    {
      "name": "priceRanges",
      "buckets": [
        { "key": "under-50", "count": 85 },
        { "key": "50-to-100", "count": 112 },
        { "key": "100-plus", "count": 43 }
      ]
    }
  ]
  "results": [...]
}
Ranges facet example.

Count facets

  • Purpose: The count facet counts the number of products (or product variants).
  • How it works: Returns a single value representing the count.
  • Key configuration:
    • name: Your unique name for this facet result.
    • scope: Whether the facet must consider only the products resulting from the search (query) or all indexed products (all).
    • filter: Additional filtering expression to apply to the facet result before calculating the facet.
    • level: Specify whether to count products (products) or product variants (variants).

You can use the count facet to get the total number of variants or products and display it in the UI as shown below.

The count for products should be the same value as the response total.
Count facet example showing total product and variant counts.

Example request

Let's get the total count of all products and all variants:

{
  "query": {},
  "facets": [
    {
      "count": {
        "name": "totalProductCount",
        "scope": "all" // Count ALL products, ignoring the main query
      }
    },
    {
      "count": {
        "name": "totalVariantCount",
        "level": "variants", // Count variants instead of products
        "scope": "all" // Count ALL variants, ignoring the main query
      }
    }
  ]
}

Example response

{
  "facets": {
    "results": [
      {
        "name": "all-products",
        "value": 200
      },
      {
        "name": "all-variants",
        "value": 1000
      }
    ]
  },
  "results": [...]
}


Test your knowledge