Product Search

Product Search enables you to create storefront discovery experiences for your customers where they can browse and search across your Products.

In contrast to Product Projection Search, Product Search supports searching through Products across Stores (up to 15 000 per Product), Product Selections (up to 15 000 per Product), and Standalone Prices (up to 10 000 per Product).
Those limits are soft, but if you go beyond the limits for Stores or Product Selections, the API will return non-deterministic results. For a large number of Standalone Prices, the API applies some sorting algorithm during product indexing to offer a balanced selection of prices.
The Product Search API accepts queries expressed in the search query language.
The API supports only those Locales configured in the Project. Queries for Locales not configured in the Project do not return any result.
Product Search does not support retrieving tailored product data. For such a scenario, use the Get ProductProjection in Store endpoints.
Only the current representation of Products are indexed for Product Search.
For performance reasons, there are some limitations that are imposed by the API. Follow our performance tips to minimize the response times for the Product Search API.
Learn more about how to implement a product listing page (PLP) using Product Search in our self-paced Build product listing pages module.

Data integration

The Product Search API is designed to be ID-first to provide high performance. This means that the query result contains only Product IDs (together with matching Product Variant IDs when requested), but not the entire resource. This design prefers lower response times over completeness of data, which can be achieved through additional methods as recommended in this section.

With GraphQL query BETA

You can use the productsSearch GraphQL query to fetch the product data for the Products in the search result.

With Product Projection API

Alternatively, you can use the Product Projection API to fetch the full representation of the Product Projections in the search result.
Use the Query ProductProjections endpoint with a filter for the IDs returned by Product Search:
Example query predicate with ID filterhttp
where=id%20in%20("ID-of-Product-1","ID-of-Product-2","ID-of-Product-3")&staged=false

You can use additional query parameters to reduce the response size to only the fields needed for rendering, for example:

  • priceCurrency, priceCountry, priceCustomerGroup, and priceChannel to filter the prices returned.
  • localeProjection to limit the Locales returned for localized fields.
  • filter[attributes] to include only specific Attributes.
  • storeProjection to include only the product availability for the specific Store and also the product data tailored for that Store.

The Product Projections returned with this query are not guaranteed to be in the same order as the Product IDs returned by Product Search. Apply the same sorting logic on the Query ProductProjections endpoint as used in the Product Search request to maintain the order.

You can also fetch each Product Projection individually using the Get ProductProjection by ID method with the ID of a matching Product.

To boost performance, we recommend to add a caching layer to persist frequently visualized product data between sessions.

The option with Product Projection parameters has been deprecated and will be removed in future. We recommend using the other data integration options instead.

Activate the Product Search API

The Product Search API is not active for the Project by default. Before first use, you have to activate it by one of the following options:

{
  "action": "changeProductSearchIndexingEnabled",
  "enabled": true,
  "mode": "ProductsSearch"
}
Once activated for your Project, your Products get indexed, and the Search Products endpoint will become fully functional soon after.

Automatic deactivation

The Product Search API will be deactivated for a Project automatically if there have been no calls against the Search Products endpoint for the duration of 30 consecutive days. The Product Search API can be reactivated again as described above.

Representations

ProductSearchRequest

query​
SearchQuery​
The search query against searchable Product fields.
sort​
Array of SearchSorting​
Controls how results to your query are sorted. If not provided, the results are sorted by relevance score in descending order.
limit​
Int​
The maximum number of search results to be returned in one page.
Default: 20​Minimum: 0​Maximum: 100​
offset​
Int​
The number of search results to be skipped in the response for pagination.
Default: 0​Minimum: 0​Maximum: 10000​
markMatchingVariants​
Boolean​
If query specifies an expression for a Product Variant field, set this to true to get additional information for each returned Product about which Product Variants match the search query. For details, see matching variants.
Default: false​
facets​
Set this field to request facets.
postFilter​
SearchQuery​
Specify an additional filter on the result of the query after the API calculated facets. This feature assists you in implementing faceted search.
Example: json
{
  "query": {
    "and": [
      {
        "fullText": {
          "field": "name",
          "language": "en",
          "value": "banana"
        }
      },
      {
        "filter": [
          {
            "exact": {
              "field": "variants.attributes.farming",
              "fieldType": "text",
              "value": "organic"
            }
          }
        ]
      }
    ]
  },
  "sort": [
    {
      "field": "name",
      "language": "en",
      "order": "desc"
    }
  ],
  "limit": 10,
  "offset": 0
}

ProductSearchFacetExpression

Refer to expression for distinct, ranges, count, or stats facets.

ProductPagedSearchResponse

total​
Int​

Total number of results matching the query.

Minimum: 0​
offset​
Int​
Number of elements skipped.
Minimum: 0​Maximum: 10000​
limit​
Int​
Minimum: 0​Maximum: 100​
facets​
Array of ProductSearchFacetResult​
Results for facets when requested.
results​
Array of ProductSearchResult​

Search result containing the Products matching the search query.

ProductSearchResult

id​
String​
id of the Product that matches the search query.
matchingVariants​
Information about which Product Variants match the search query. Only present if markMatchingVariants is set to true in the ProductSearchRequest.

ProductSearchMatchingVariants

allMatched​
Boolean​
true if all Variants of the returned Product match the search query, or if search query does not specify any expression for a Product Variant field.
false if only a subset of the Product Variants match the search query.
Is always false for query expressions on Product Variant fields.
matchedVariants​

Identifiers of the Product Variants that match the search query.

Empty if all Product Variants of the returned Product match.

ProductSearchMatchingVariantEntry

id​
Int​
id of the ProductVariant that matches the search query.
sku​
String​
sku of the ProductVariant that matches the search query.

ProductSearchFacetResult

ProductSearchFacetResultCount

Result of a count facet.
name​
String​

Name of the facet.

value​
Int​

Number of Products (or Product Variants) matching the query.

ProductSearchFacetResultBucket

Result of a distinct facet or a ranges facet.
name​
String​

Name of the facet.

buckets​

Contains results of the facet.

ProductSearchFacetResultBucketEntry

key​
String​

Key of the bucket.

count​
Int​

Number of values in the bucket.

ProductSearchFacetResultStats

Result of a stats facet. The data type of min max, mean, and sum matches the data type of the field in the facet expression.
name​
String​

Name of the facet.

min​
Any​

The minimum value of the field, scoped to the faceted results.

max​
Any​

The maximum value of the field, scoped to the faceted results.

mean​
Any​
The average value of the field calculated as sum / count.

Only returned for number fields.

sum​
Any​
The sum of values of the field that match the facet expression.

Only returned for number fields.

count​
Int​

The total number of values counted that match the facet expression.

Search Products

POST
https://api.{region}.commercetools.com/{projectKey}/products/search
If indexing is in progress or if Product Search is inactive, an ObjectNotFound error is returned. If inactive, you can reactivate it.
OAuth 2.0 Scopes:
view_published_products:{projectKey}
Path parameters:
region
​
String
​
Region in which the Project is hosted.
projectKey
​
String
​
key of the Project.
Request Body:ProductSearchRequestasapplication/json
Response:
200

ProductPagedSearchResponse

as
application/json
Request Example:cURL
curl https://api.{region}.commercetools.com/{projectKey}/products/search -i \
--header "Authorization: Bearer ${BEARER_TOKEN}" \
--header 'Content-Type: application/json' \
--data-binary @- << DATA 
{
  "query" : {
    "and" : [ {
      "fullText" : {
        "field" : "name",
        "language" : "en",
        "value" : "banana"
      }
    }, {
      "filter" : [ {
        "exact" : {
          "field" : "variants.attributes.farming",
          "fieldType" : "text",
          "value" : "organic"
        }
      } ]
    } ]
  },
  "sort" : [ {
    "field" : "name",
    "language" : "en",
    "order" : "desc"
  } ],
  "limit" : 10,
  "offset" : 0
}
DATA
200 Response Example: ProductPagedSearchResponsejson
{
  "total": 148,
  "offset": 0,
  "limit": 10,
  "facets": [
    {
      "name": "countProducts",
      "buckets": [
        {
          "key": "white",
          "count": 37
        }
      ]
    },
    {
      "name": "countVariants",
      "buckets": [
        {
          "key": "white",
          "count": 301
        }
      ]
    }
  ],
  "results": [
    {
      "id": "8fde2af0-6a2f-4633-9ba4-83566f769a7f",
      "matchingVariants": {
        "allMatched": false,
        "matchedVariants": [
          {
            "id": 1,
            "sku": null
          }
        ]
      }
    }
  ]
}

Searchable Product fields

This section lists the fields of the Product search index that can be used for the field property in simple expressions. For standard fields on Products, the type of field determines which simple expressions are supported for the field.
The variants.* fields contain the data for all Product Variants (including the Master Variant) for a Product.
The variants.prices.* fields contain either Embedded Prices or Standalone Prices, depending on the Product's priceMode.

Attributes

The SearchFieldType of the Attribute determines the simple expressions in which you can use the Attribute specified by:
  • attributes.<attribute-name> (for Product Attributes)
  • variants.attributes.<attribute-name> (for Variant Attributes)
To query for multiple values of the same field or for several Attributes in the same request, combine the simple expressions with a compound expression.
An Attribute must be declared as searchable in its AttributeDefinition.

Boolean fields

Use the following boolean type fields on Products in exact and exists expressions.
Standard fieldQuery for
variants.availability.isOnStockProductVariants that are available in stock.
variants.prices.discountedProductVariants with a discounted Price.

Number and date fields

Use the following fields in exact, exists, and range expressions. You can request ranges and stats facets for fields of this type. Data type indicates the type of field for the field.
Standard fieldData typeQuery for
reviewRatingStatistics.averageRatingdoubleProducts with a certain average review rating.
reviewRatingStatistics.highestRatingdoubleProducts with a certain maximum in review rating.
reviewRatingStatistics.lowestRatingdoubleProducts with a certain minimum in review rating.
reviewRatingStatistics.countlongProducts with a certain number of reviews.
variants.prices.currentCentAmountlongProductVariants with a Price of certain cent amount taking the discounted price into account.
variants.prices.centAmountlongProductVariants with a Price of certain cent amount not taking the discounted price into account.
variants.availability.availableQuantitylongProductVariants with a certain available quantity.
variants.prices.validFromdateTimeProductVariants with a Price valid from a certain date and time.
variants.prices.validUntildateTimeProductVariants with a Price valid until a certain date and time.
createdAtdateTimeProducts created at a certain date and time.
lastModifiedAtdateTimeProducts last modified at any of its fields at a certain date and time.

Keyword fields

Use the following keyword type fields, containing unique IDs or keys, in exact, prefix, wildcard, and exists expressions. To query for multiple values of the same field or for several fields in the same request, combine the simple expressions with a compound expression.
Standard fieldQuery for
ida Product with a specific id.
keya ProductVariant with a specific key.
productTypea ProductVariant of a specific ProductType identified by its id.
taxCategorya ProductVariant with a specific TaxCategory identified by its id.
statea ProductVariant with a specific State identified by its id.
categoriesa ProductVariant that has a specific Category, identified by its id, assigned. A query for this field also returns Products that have more Categories assigned than the one specified.
categoryOrderHints.{categoryID}an entry in the Product's CategoryOrderHints identified by its Category id.
categoriesSubTreea ProductVariant that has a specific Category, identified by its id, or one of its subcategories assigned.
variants.idProductVariants with a specific id.
variants.keya ProductVariant with a specific key.
variants.skua ProductVariant with a specific sku.
variants.prices.idProductVariants with a specific Price identified by its id.
variants.prices.currencyCodeProductVariants with prices for a specific currency formatted as CurrencyCode.
variants.prices.countryProductVariants with prices for a specific country formatted as CountryCode.
variants.prices.customerGroupProductVariants with a Price for a specific CustomerGroup identified by its id.
variants.prices.channelProductVariants with a Price for a specific Channel identified by its id.
variants.availability.isOnStockForChannelProductVariants that are available in stock for a specific Channel identified by its id.
variants.availability.hasInventoryEntryForChannel BETAProductVariants that have an InventoryEntry for a specific Channel identified by its id, regardless their availability in stock.
variants.productSelections BETAa Product Selection identified by its id that a Product Variant is assigned to.
variants.stores BETAa Store identified by its id that a Product Variant is available in.
productSelectionsa Product Selection identified by its id that a Product is assigned to.
storesa Store identified by its id that a Product is available in.

Text and localized text fields

Use the following localizedText type fields in exact, fullText, prefix, fuzzy BETA, wildcard, and exists expressions.
Standard fieldData typeQuery for
namelocalizedTextProducts with a specific name for a specific Locale.
sluglocalizedTexta ProductVariant with a specific slug for a specific Locale.
descriptionlocalizedTextProducts with a specific description for a specific Locale.
searchKeywordslocalizedTextProducts with specific searchKeywords for a specific Locale.

Example query:

The following example demonstrates how to find Product Variants with a price of EUR 22.22:

Requestjson
{
  "query": {
    "and": [
      {
        "exact": {
          "field": "variants.prices.currencyCode",
          "value": "EUR"
        }
      },
      {
        "exact": {
          "field": "variants.prices.centAmount",
          "value": 2222
        }
      }
    ]
  }
}

Matching variants

For query expressions on searchable fields that refer to the Product as a whole (for example, categories, key, slug), all variants of the Products returned in the ProductSearchResult match the query expression by default.
For query expressions on Product Variant-specific searchable fields, (for example, variants.attributes.*, variants.prices.*), the expression may match only for certain Product Variants.
To find out which of the Product Variants match the search query, we recommend setting markMatchingVariants to true in the ProductSearchRequest. This will cause the response to include the matchingVariants field which contains the id and sku of the Product Variants that match the query.
Returned Product matching price level field requested with markMatchingVariants: truejson
  ...
        {
            "id": "{{ID-of-the-matching-Product}}",
            "matchingVariants": {
                "allMatched": false,
                "matchedVariants": [
                    {
                        "id": 1,
                        "sku": "CSKW-093"
                    },
                    {
                        "id": 5,
                        "sku": "CSKP-0932"
                    },
                    {
                        "id": 8,
                        "sku": "CSKG-023"
                    }
                ]
            }
        },
  ...
If all Product Variants match the search query, each item in the search result will contain a matchingVariants object with allMatched set to true, but it will not explicitly list all variants.
Returned Product for which all Variants match, requested with markMatchingVariants: truejson
  ...
        {
            "id": "{{ID-of-the-matching-Product}}",
            "matchingVariants": {
                "allMatched": true,
                "matchedVariants": []
            }
        },
  ...
For a not expression, the API returns only the Products for which no Product Variant matches the nested simple expressions.
For example, you want to search for Products that don't have the commonSize attribute set. The following not-exists expression will return only Products for which none of the Product Variants have the commonSize attribute set. The API will not return Products that have at least one variant with this attribute.
Requestjson
{
  "query": {
    "not": [
      {
        "exists": {
          "field": "variants.attributes.commonSize.key",
          "fieldType": "enum"
        }
      }
    ]
  }
}

Query validation

During query validation, the API analyzes the search query and evaluates its expressions and the validity of its searchable fields.

Field levels

For query validation, the API organizes the indexed product information into three different levels:

Field levelSearchable Product fieldsRank
context levelstores, productSelections1
product levelall Product fields, for example key, name, productType, all Product Variant fields (variants.*) except for prices2
price levelall price-related fields (variants.prices.*)3

Validation of compound expressions

To ensure valid search results, the API enforces field-level constraints on compound expressions. All criteria within a compound expression must refer to the same field level. Queries violating this constraint will be rejected with a 400 status code, indicating an invalid combination of criteria.

Query template

The following generic query template ensures compliance with the query validation:

Query templatejson
  "query": {
    "and" | "or": [
      <store criterion>,
      <product-selection criterion>,
      <product/variant criterion>,
      <price criterion>
    ]
  }
Each criterion can be a simple or compound expression that applies only to searchable fields at that field level.

If your use case requires mixing field levels in compound expressions, you can still formulate valid queries if they follow the validation rules explained in the next section.

Validation logic details

The validation logic checks if all expressions in a compound expression can be combined successfully. When validating a compound expression, the verification is performed iteratively level by level in following order:

price level --> product level --> context level

Consider following example compound expression in simplified syntax, highlighting the searchable fields and omitting the values.

Example compound expressionbash
and(
  exact("stores", ...),
  exact("productSelections", ...),
  or(exact("variants.attributes.color", ...), fullText("description", ...)),
  or(exact("variants.prices.country", ...), exact("variants.prices.channel", ...)),
  exact("variants.prices.currencyCode", ...)
)
The API validates this query beginning from the price level expressions, continuing with the product level expressions and finishing with the context level expressions as shown in the following diagram.

Valid compound expressions

The API employs a hierarchical field level validation for compound expressions. Validation proceeds by first determining the field level among the constituent simple expressions. Which level is then propagated to the compound expression and used in subsequent validation steps, depends on following cases.
  • If both expressions are simple expressions on the same field level, the compound result is on single-level with the same rank as the simple expressions.
  • If the expressions are simple expressions on different field levels, the compound result is marked as multi-level inheriting the rank of the lowest field level among its constituents.
  • If one expression is multi-level and the other expression is single-level on the same level, the compound result is on the same multi-level.
  • If one expression is multi-level and the other expression is single-level with a lower rank, the compound result is multi-level with the lowest rank among its constituents.
Validation rule:

The rank of the multi-level expression must be higher or equal to the rank of the single-level expression.

Invalid compound expressions

  • If one expression is multi-level and the other expression is single-level with a higher rank, the query is invalid and the API responds with an InvalidInput error.
  • If both expressions are multi-level the query is invalid, regardless of the ranks, and the API responds with an InvalidInput error.

The error message lists the query expressions that failed the query validation. From the searchable fields listed in the error message, you can determine the level for each expression and analyze where it invalidates the query validation.

Example error message - formatted for better analysisbash
Expressions nesting level are incompatible:
expr1=
[ and(
      exact("variants.prices.currencyCode", ...),
      exact("variants.prices.channel", ...)
  )
],
expr2=[
  or(
    and(
      fullText("variants.parentProduct.name", ...),
      exact("variants.parentProduct.productType", ...)
    ),
    exact("variants.prices.country", ...)
  )
]

Facets

Facets give you statistical information about your data. It answers questions like "how many t-shirts of size XL are there?" or "how many TVs are there that cost between US$500 and $600?" The first question is answered by a distinct facet, and the second one by a ranges facet. Each facet has a name assigned to it, so it can be found in the ProductSearchFacetResult.
If you only want to view the facets, set limit to 0 in the ProductSearchRequest.

Distinct facets

The distinct facet counts occurrences of distinct values.

ProductSearchFacetDistinctExpression

distinct​

Definition of the distinct facet.

ProductSearchFacetDistinctValue

name​
String​
Name of the distinct facet to appear in the ProductSearchFacetResultBucket.
scope​
Whether the facet must consider only the Products resulting from the search (query) or all the Products (all).
Default: query​
filter​
SearchQuery​

Additional filtering expression to apply to the facet result before calculating the facet.

Specify whether to count Products (products) or Product Variants (variants).
Default: products​
field​
String​
The searchable Product field to facet on.
includes​
Array of String​

Specify which bucket keys the facets results should include.

sort​

Define how the buckets are sorted.

limit​
Int​

Maximum number of buckets to return.

Default: 10​Maximum: 200​
language​
Locale​
fieldType​
If the field is not standard, this must be the Attribute type.
missing​
String​

Default value to use if the specified field is not present on some Products.

Example:

The following example calculates facets for distinct values within an Attribute called size. The response shows there are 112 Products of size 43, 63 Products of size 44, and 34 Products of size 45.
Requestjson
{
  "facets": [
    {
      "distinct": {
        "name": "sizes",
        "field": "variants.attributes.size",
        "fieldType": "number",
        "limit": 50
      }
    }
  ]
}
Responsejson
{
  "facets": {
    "results": [
      {
        "name": "sizes",
        "buckets": [
          {
            "key": "43",
            "count": 112
          },
          {
            "key": "44",
            "count": 63
          },
          {
            "key": "45",
            "count": 34
          }
        ]
      }
    ]
  }
}

Ranges facets

The ranges facet counts Products that have values within a specified range. These values must be numeric or of type DateTime. You can specify ranges using from and to. Open ranges can be specified by omitting either the from or to values.

ProductSearchFacetRangesExpression

ranges​

Definition of the ranges facet.

ProductSearchFacetRangesValue

name​
String​
Name of the ranges facet to appear in the ProductSearchFacetResultBucket.
scope​
Whether the facet must consider only the Products resulting from the search (query) or all the Products (all).
Default: query​
filter​
SearchQuery​

Additional filtering expression to apply to the facet result before calculating the facet.

Specify whether to count Products (products) or Product Variants (variants).
Default: products​
field​
String​
The searchable Product field to facet on.
ranges​

Define ranges for the facet.

language​
Locale​
fieldType​
If the field is not standard, this must be the Attribute type.

ProductSearchFacetRangesFacetRange

Values for from and to must be a number or DateTime.
key​
String​

Key to assign the bucket.

from​
Any​

Starting value of the bucket (inclusive).

to​
Any​

Ending value of the bucket (non-inclusive).

Example:

The following example calculates the number of Products which have the Attribute screenDiagonal within a certain range. The response shows that there are 33 TVs with a screenDiagonal lower than 40, and 165 TVs with a screenDiagonal between 40 and 55.
Requestjson
{
  "facets": [
    {
      "ranges": {
        "name": "screenDiagonalRanges",
        "field": "variants.attributes.screenDiagonal",
        "fieldType": "number",
        "ranges": [
          {
            "to": 40
          },
          {
            "from": 40,
            "to": 55
          },
          {
            "from": 55
          }
        ]
      }
    }
  ]
}
Responsejson
{
  "facets": {
    "results": [
      {
        "name": "screenDiagonalRanges",
        "buckets": [
          {
            "key": "*-40",
            "count": 33
          },
          {
            "key": "40-55",
            "count": 165
          },
          {
            "key": "55-*",
            "count": 82
          }
        ]
      }
    ]
  }
}
You can rename the key values in the response by including them in ranges. The following example shows the ranges renamed to small, medium, and large.
Responsejson
{
  "facets": [
    {
      "ranges": {
        "name": "screenDiagonalRanges",
        "field": "variants.attributes.screenDiagonal",
        "fieldType": "number",
        "ranges": [
          {
            "key": "small",
            "to": 40
          },
          {
            "key": "medium",
            "from": 40,
            "to": 55
          },
          {
            "key": "large",
            "from": 55
          }
        ]
      }
    }
  ]
}

Count facets

The count facet counts the number of Products (or Product Variants).

ProductSearchFacetCountExpression

count​

Definition of the count facet.

ProductSearchFacetCountValue

name​
String​
Name of the count facet to appear in the ProductSearchFacetResultCount.
scope​
Whether the facet must consider only the Products resulting from the search (query) or all the Products (all).
Default: query​
filter​
SearchQuery​

Additional filtering expression to apply to the facet result before calculating the facet.

Specify whether to count Products (products) or Product Variants (variants).
Default: products​

ProductSearchFacetCountLevelEnum

products

The query should count Products.

variants

The query should count Product Variants.

Example:

The following example request contains a count facet for the number of Products as well as one for the number of Product Variants. The example response shows the result of these count facets (200 Products and 1000 Product Variants).
Requestjson
{
  "facets": [
    {
      "count": {
        "name": "all-products"
      }
    },
    {
      "count": {
        "name": "all-variants",
        "level": "variants"
      }
    }
  ]
}
Responsejson
{
  "facets": {
    "results": [
      {
        "name": "all-products",
        "value": 200
      },
      {
        "name": "all-variants",
        "value": 1000
      }
    ]
  }
}

Stats facets

Stats facets return statistical aggregations such as minimum, maximum, average (mean), sum, and count on values of number and date fields in your search results. This allows you to gain insights into your product data without calculating them client-side based on several queries.

ProductSearchFacetStatsExpression

stats​

Definition of the stats facet.

ProductSearchFacetStatsValue

name​
String​
Name of the stats facet to appear in the ProductSearchFacetResultStats.
scope​
Whether the facet must consider only the Products resulting from the search (query) or all the Products (all).
Default: query​
filter​
SearchQuery​

Additional filtering expression to apply to the search result before calculating the facet.

field​
String​
The searchable Product field to facet on.
fieldType​
If the field is not standard, this must be the Attribute type.

Example:

The following example calculates facets for stats values for the Prices centAmount property. The response shows there are 3 (i.e.: count) centAmount prices, with a min value of 100, a max value of 300, an average (mean) value of 200, a sum of 600.
Requestjson
{
  "facets": [
    {
      "stats": {
       "name": "pricesStatsVariants",
       "field": "variants.prices.centAmount"
      }
    }
  ]
}
Responsejson
{
  "facets": {
    "results": [
      {
        "name": "pricesStatsVariants",
        "min": 100,
        "max": 300,
        "mean": 200,
        "sum": 600,
        "count": 3
      }
    ]
  }
}

Example:

The following example requests stats for the Prices validFrom property. The response shows the earliest date in the min and the latest date in the max field. Average and sum are not calculated for date and date time values.
Requestjson
{
  "facets": [
    {
      "stats": {
       "name": "pricesValidFromStats",
       "field": "variants.prices.validFrom"
      }
    }
  ]
}
Responsejson
{
  "facets": {
    "results": [
      {
        "name": "pricesValidFromStats",
        "min": "2001-09-11T14:00:00.0Z",
        "max": "2024-05-01T04:00:00.0Z",
        "count": 7
      }
    ]
  }
}

Operations on facets

You can modify facets with following operations to filter and sort them, for instance.

Select specific buckets key

Use includes to specify which bucket keys the facet results should include.

Example:

The following example counts the number of Products that have the Categories 53992aac-cbbf-4b97-bb-09142435e1ea and 344c8ceb-01ac-4d76-8f93-bbbc3d0edd5d assigned.
Requestjson
{
  "facets": [
    {
      "distinct": {
        "name": "categories",
        "field": "categories",
        "includes": [
          "53992aac-cbbf-4b97-bb-09142435e1ea",
          "344c8ceb-01ac-4d76-8f93-bbbc3d0edd5d"
        ]
      }
    }
  ]
}
Responsejson
{
  "facets": {
    "results": [
      {
        "name": "categories",
        "buckets": [
          {
            "key": "53992aac-cbbf-4b97-bb-09142435e1ea",
            "count": 243
          },
          {
            "key": "344c8ceb-01ac-4d76-8f93-bbbc3d0edd5d",
            "count": 12
          }
        ]
      }
    ]
  }
}

Missing values

If the field defined in distinct.field is optional, then all the Products without a value for this field will be ignored by default.
To include the count of Products with missing values, add a missing key and value. The value of missing will be used as the key for the count of Products without a value for distinct.field.

Example:

The following example calculates the number of Products in a specific State. The number of Products without a State is indicated with the key N/A. All other keys represent the id of a State, with the values specifying the count of Products in that State.
Requestjson
{
  "facets": [
    {
      "distinct": {
        "name": "states",
        "field": "state",
        "missing": "N/A",
        "limit": 50
      }
    }
  ]
}
Responsejson
{
  "facets": {
    "results": [
      {
        "name": "states",
        "buckets": [
          {
            "key": "N/A",
            "count": 112
          },
          {
            "key": "995c735c-ad07-4d22-a35b-34e8f5238fd74",
            "count": 63
          },
          {
            "key": "fb77d7dc-94c5-45df-95bc-b8499286e4d55",
            "count": 34
          }
        ]
      }
    ]
  }
}
The total count of keys in the result is still less than or equal to the distinct.limit

Sort buckets in the facets results

The optional sort field allows sorting the bucket results in the facets results. You can sort buckets either by count or key and specify the order of the sort by asc or desc.

ProductSearchFacetDistinctBucketSortExpression

Defines whether to sort by bucket count or key.

order​

Defines the sorting order.

ProductSearchFacetDistinctBucketSortBy

count

Sort buckets by the count value.

key

Sort buckets by the bucket key.

Example:

The following example sorts an Attribute called "name" by key in descending order.
Requestjson
{
  "facets": [
    {
      "distinct": {
        "name": "attributes",
        "field": "variants.attributes.name",
        "sort": {
          "by": "key",
          "order": "desc"
        }
      }
    }
  ]
}
Responsejson
{
  "facets": {
    "results": [
      {
        "name": "attributes",
        "buckets": [
          {
            "key": "z-attribute",
            "count": 1
          },
          {
            "key": "b-attribute",
            "count": 2
          },
          {
            "key": "a-attribute",
            "count": 1
          }
        ]
      }
    ]
  }
}

Count entity for distinct and ranges facet

When faceting you might be interested in counting the number of Products (default) or Product Variants that fall into a particular bucket. You can specify the counting level via the count parameter of distinct and ranges facets.

Example:

In the following example, given 10 Products each with 5 Product Variants, the sizesByProducts buckets will have a count of 10 (each Product has an item of each size) while the sizesByVariants buckets will have a count of 50 (each Product Variant of a given size is counted).
Requestjson
 "facets": [
    {
      "distinct": {
        "name": "sizesByProducts",
        "fieldType": "number",
        "field": "variants.attributes.size",
        "count": "products"
      }
    },
    {
      "distinct": {
        "name": "sizesByVariants",
        "fieldType": "number",
        "field": "variants.attributes.size",
        "count": "variants"
      }
    }
  ]

Scope of facets

By default, all facets run in the scope of a query. That means if you query, for example, for Products that have been last modified before a certain date and in that same query you have a facet, this facet will only count Products that match the query.

Product Search provides two ways of scoping the facets: the global facet and the filter facet. You can combine both to accurately define the scope of your facets.

ProductSearchFacetScopeEnum

all

Count all Products (or Product Variants) without considering the search query.

query

Only count the Products (or Product Variants) that match the search query.

Global facet

To run a facet in global scope, set scope to all.

Example:

In the following example, the "names" facet aggregates all Products, not only the ones with "butter" in their description.

Requestjson
{
  "query": {
    "fullText": {
      "field": "description",
      "value": "butter",
      "language": "en"
    }
  },
  "facets": [
    {
      "distinct": {
        "scope": "all",
        "name": "names",
        "field": "name",
        "language": "en",
        "limit": 50
      }
    }
  ]
}

Filter facet

If you need to run a facet on a subset of Products that is different from the result of the query, you can use a filter facet.

Example:

In the following example, androidTabletOSVersions is scoped only to Products that have the Attribute isTablet set to true.
Requestjson
{
  "query": {
    "exact": {
      "field": "variants.attributes.os",
      "fieldType": "text",
      "value": "Android"
    }
  },
  "facets": [
    {
      "distinct": {
        "name": "androidTabletOSVersions",
        "field": "variants.attributes.osVersion",
        "fieldType": "text",
        "limit": 50,
        "filter": {
          "exact": {
            "field": "variants.attributes.isTablet",
            "fieldType": "boolean",
            "value": true
          }
        }
      }
    }
  ]
}

Sorting

Sorting allows you to control how the results of your search query are sorted. If no sort is specified, the results are sorted by relevance score in descending order.

Sort by category order hints for a specific category.json
{
  ...
  "sort": [
    {
      "field": "categoryOrderHints.64daee6a-c0e1-42e5-b7a4-a597610b1218",
      "order": "desc"
    }
  ]
}

Compound sorting

Compound sorting (also known as multi-sort) is applied when you specify multiple sort expressions in order of priority, with each expression having its own sort direction. See the following example:

First, sort by price in ascending order, and then by relevance score in descending order.json
{
  ...
  "sort": [
    {
      "field": "variants.prices.centAmount",
      "order": "asc",
      "mode": "min"
    },
    {
      "field": "score",
      "order": "desc"
    }
  ]
}

Sort filters

Sort filters cannot be used with context level fields.

Sort filters can be used to sort returned Products by criteria that are specific to certain Product Variants. For example, you can specify that only those variants with a price scoped to a certain channel and currency should be considered for sorting:

Sort by scoped pricejson
{
  ...
  "sort": [
    {
      "field": "variants.prices.centAmount",
      "filter": {
        "and": [
          {
            "exact": {
              "field": "variants.prices.channel",
              "value": "fb16244b-3963-4b9e-9cb0-69a1f563a854"
            }
          },
          {
            "exact": {
              "field": "variants.prices.currencyCode",
              "value": "EUR"
            }
          }
        ]
      },
      "order": "asc"
    }
  ]
}
In addition to the scoped price, the following example limits sorting to variants where the color attribute is red:
Sort by scoped price of variants with red colorjson
{
  ...
  "sort": [
    {
      "field": "variants.prices.centAmount",
      "filter": {
        "and": [
          {
            "exact": {
              "field": "variants.attributes.color",
              "fieldType": "text",
              "value": "red"
            }
          },
          {
            "exact": {
              "field": "variants.prices.channel",
              "value": "fb16244b-3963-4b9e-9cb0-69a1f563a854"
            }
          },
          {
            "exact": {
              "field": "variants.prices.currencyCode",
              "value": "EUR"
            }
          }
        ]
      },
      "order": "asc"
    }
  ]
}
For more information about sorting Product Search results, see the documentation for the search query language.

Pagination

A response to the search request contains the first 20 results by default. Pagination allows you to retrieve the first 10 000 results by requesting them page by page. The total field in a query result indicates how many results match the search query in total.

Limit

The limit field allows you to set the maximum number of results returned on a page. Any value between 0 and 100 is allowed, the default limit is 20.

Offset

The offset field allows you to control which page number you want to retrieve. The default value is 0, meaning you retrieve the first page of query results containing as many results as specified by limit. A value of 1 will skip the first page of results, and your result contains the second bucket of results with the specified limit.
The maximum offset is between 9 900 and 10 000 depending on the limit parameter. That is, if you set the limit parameter to its maximum value of 100, the maximum offset is 9 900. If you use the default page limit of 10, you can set the maximum offset value to 9 990.
Since you can only retrieve the first 10 000 results in total, a higher offset would exceed that limit. In such a case, the API responds with an InvalidInput error with the message "Pagination cannot be used to fetch more than the first 10000 results."

Product indexing

The Product Search API uses a dedicated search index to provide fast and relevant search results. To prevent your storefront from exposing staged product data, only the current representation of Products is indexed for Product Search by design. Product Search indexes Attributes and Prices of published Products based on specific limits to ensure optimal performance.

Attributes

Product Search indexes both Product Attributes BETA and Variant Attributes for which the AttributeDefinition's isSearchable field is set as true. Only Attributes that are present in the Product or Product Variant are indexed. If a Product Type defines an Attribute but the corresponding Product Variant does not have a value for this Attribute, then this Attribute is not indexed.

Large number of Attributes

For performance reasons, the number of indexed Attributes per Product Variant is limited to 50. The limit applies to both Product Attributes and Variant Attributes separately. For example, if a Product Variant has 42 Variant Attributes and the respective Product has additional 15 Product Attributes, then all 57 Attributes are indexed.
If the number of searchable Attributes present in a Product Variant exceeds this limit, the indexer applies the following strategy to make 50 Attributes available for Product Search.

Indexing strategy

For each Product Variant, the indexer:

  • Counts the number of Attributes used on the Product Variant in the order they are defined on the corresponding Product Type.
  • Skips any Attribute that is not present on the Product Variant.
  • Skips any Attribute that is present but not marked as searchable.
  • Indexes any Attribute that is present and marked as searchable.
  • Continues until the limit of 50 Attributes is reached.

Example

Consider an example Product Type that has 100 Attributes (attr_1, ..., attr_100) defined and all of them are marked as searchable.
  • Given a Product Variant that has all Attributes of this Product Type in use, Product Search would index the first 50 Attributes (from attr_1 to attr_50). The remaining Attributes (attr_51 to attr_100) would not be indexed due to the limit.
  • Let's say there is another Product Variant that uses 60 Attributes from the example Product Type (attr_21 to attr_80). Product Search would skip the first 20 Attributes since they are not present on the Product Variant, and would start from attr_21 this time. The indexer would then finish with attr_70 because the limit has been reached leaving out attr_71 to attr_80.

Product Attributes BETA

The indexing strategy is applied separately for Product Attributes on Products when they exceed the limit of 50. Overall, Product Search can index up to (50 + 50) Attributes per Product Variant.

Prices

The Product's priceMode determines which Prices are indexed in the variants.prices field.
For the Embedded ProductPriceMode, the API indexes Embedded Prices; for Standalone, the API indexes the Standalone Prices associated with the Product Variant.
The API uses the variants.prices field for sorting, filtering, and faceting on Products, but its content is only retrievable through the GraphQL API or the Product Projection API for Embedded Prices. Standalone Prices cannot be retrieved through the Product Search API.
To ensure good performance of the API, the number of Standalone Prices indexed per Product is limited to 10 000. For Products with Standalone Prices that exceed this limit, the system indexes a selection of the Prices as explained in the Large number of Standalone Prices section.
Only one valid price for each price scope (currency, country, customerGroup, and distributionChannel) is actually indexed. Like other attributes, this process operates on an eventually consistent basis, meaning that invalid prices might still be present in the index immediately after an update. When trying later, the price will be updated.

In case of multiple prices for the same scope, the one with the narrower (valid) time range is always selected over the price without time ranges.

Large number of Standalone Prices

The search index can hold up to 10 000 Standalone Prices per Product. In case you have Products with more Standalone Prices than that limit, the API applies the following sorting algorithm when indexing the Product Prices to achieve a fair distribution of indexed Prices across all Product Variants:

For each Product in the Project:

  1. Calculate variant limit by dividing 10 000 by the number of Variants on the Product.
  2. fetch Prices for all Variants.
  3. for each Variant:
  • sort Prices by

    • currency (alphabetically by the value.currencyCode)
    • country (alphabetically, missing field is sorted as first)
    • channel (alphabetically by key, missing field is sorted as first)
    • Customer Group (alphabetically by key, missing field is sorted as first)
  • truncate Prices that are beyond the variant limit

Prices with validity dates

The Price with the most exact data and price scope is indexed for a Product Variant. This means that a Price with a valid time range is given priority over a Price without a validity period. Prices that are not valid at the time of indexing are not considered for indexing. Product Search does not automatically update the search index when a Price reaches its validity date or becomes invalid.

If your use case requires prices with validity dates, please contact our Composable Commerce support team. We will help you configure your Project to ensure proper handling of price updates based on validity dates.

Stores

The search index can hold up to 15 000 Stores per Product. If a Product belongs to more than 15 000 Stores, the API will return non-deterministic results for expressions on the stores keyword field.

Product Selections

The search index can hold up to 15 000 Product Selections per Product. If a Product belongs to more than 15 000 Product Selections, the API will return non-deterministic results for expressions on the productSelections keyword field.

Localized content

To support exact, full text, prefix, fuzzy BETA, and wildcard expressions, the Product Search API analyzes the localized product data specific to the supported languages.

Language settings

When Products are indexed, the Product Search API selects the best-fitting language-specific analyzer according to the Project settings. If no languages are set on the Project, the analyzers for the following languages are used by default: en, en-GB, en-CA, en-US, en-AU, de, de-DE, es, se, fi, da, fr, and fr-FR.
Any language that is specified on the Product field, but not defined in the Project settings, will make that localized Product field not searchable.
Example: A Project has language settings defined for en and de, and a Product has localized name fields, one for name.ro and one for name.de. Since the Romanian language is not part of the Project language settings, the name.ro field is not searchable, only the name.de field is in this case.

Supported languages

subtaglanguage
arArabic
caCatalan
csCzech
daDanish
deGerman
elGreek
enEnglish
esSpanish
euBasque
faPersian
fiFinnish
frFrench
hiHindi
huHungarian
hyArmenian
idIndonesian
itItalian
koKorean
lvLatvian
nlDutch
noNorwegian
plPolish
ptPortuguese
roRomanian
ruRussian
skSlovak
svSwedish
thThai
trTurkish
zhChinese

Index updates

Index updates by Region
  • Europe Google Cloud, Australia Google Cloud, and North America Google Cloud: The indexing process follows the description in this section.
  • All other regions: Indexing occurs on average every 15 minutes. However, the actual time for product data to be fully updated may be longer depending on the volume of data being processed.

Our search infrastructure uses an event-driven indexing process to ensure product and price information remains current. The indexing method depends on the type of operation on the product catalog:

Incremental indexing

The following changes will cause an incremental indexing:

  • A creation, update, or deletion of a Product, a Product Variant or a Price (Embedded or Standalone).
  • An activation or deactivation of a Product Discount leading to changes on Prices.
  • An addition of Products or Product Variants to a Product Selection (same for removal).

On average, an incremental index update is performed within a few minutes. Depending on the amount of Products to be updated, for example in a batched process, the actual delay—until all the Products are up-to-date—can be higher.

Incremental indexing is paused while a full reindexing is in progress.

Full reindexing

The following changes will require a full reindexing:

  • A creation, update, or deletion of a Product Type.
  • A creation, update, or deletion of a Store.
  • A deletion of a Product Selection.

On average, a full reindex is performed within 15 minutes. The actual delay—until the product data is up-to-date—can be higher and depends on the following factors:

  • The number of Products in the catalog.
  • The number and size of the Product Variants of the Products.
  • The number of Standalone Prices used in the Project.
  • The number of Product Selections and Stores used in the Project.