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), Product Selections (up to 15 000), and Standalone Prices (up to 10 000). 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 before returning a result. The API supports only those Locales configured in the Project. If missing, the search requests do not return any result.

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) by default, 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. To integrate the search results of this API with more product data, you have application-side as well as API-wise options.

With application-side caches

If possible, use application-side caches to store the IDs and SKUs returned with the search results together with the corresponding product data.

With Product Projection API

We recommend using the product GraphQL query with the returned Product IDs to retrieve the data you exactly require for your use case.

You can also use the Get ProductProjection by ID endpoint to fetch the entire product data of a matching Product.

With Product Projection parameters

If the aforementioned retrieval and caching options are not feasible for your use case, the Product Search API offers some built-in integration with the Product Projection API also. To do this, provide the productProjectionParameters field with the ProductSearchRequest and the ProductSearchResult contains the projected product data in an additional productProjection field. This integration option prevents you from making an additional API call to the Product Projection API, but the search request takes longer to respond. A best-effort caching approach is in place for this integration but without guarantees on the freshness or possibility of caching the product data.

Activation of the feature

The Product Search feature is not active for the Project by default. Before first use, you have to activate it by using the Change Product Search Indexing Enabled update action with mode: ProduductsSearch on the Update Project endpoint.

{
"action": "changeProductSearchIndexingEnabled",
"enabled": true,
"mode": "ProductsSearch"
}

As soon as the Product Search feature is activated for your Project, its Products get indexed, and the Search Products endpoint will become fully functional soon after.

Automatic deactivation

The Product Search feature 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 feature can be reactivated again as described above.

Search Products

If this endpoint responds with Error 404 "no index found for project", the feature has been deactivated due to inactivity and must be reactivated before API requests will succeed.

POST
https://api.{region}.commercetools.com/{projectKey}/products/search
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:
200ProductPagedSearchResponseasapplication/json
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
}
]
}
}
]
}

Representations

ProductSearchRequest

query

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 in descending order.

limit
Int

The maximum number of search results to be returned.

Default: 20Maximum: 100
offset
Int

The number of search results to be skipped in the response for pagination.

Default: 0Maximum: 9 900
markMatchingVariants
Boolean

The search can return Products where not all Product Variants match the search criteria. If true, the response will include a field called matchingVariants that contains the sku of Product Variants that match the search query. If the query does not specify any variant-level criteria, matchingVariants will be null signifying that all Product Variants are a match.

Default: false
productProjectionParameters

Set this field to {} to get the ProductProjection included in the ProductSearchResult. Include query parameters for controlling Reference Expansion or projections according to your needs. If not set, the result does not include the Product Projection.

facets

Set this field to request facets.

postFilter

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
}

ProductSearchProjectionParams

expand
Array of Expansion

Expands a value of type Reference in a ProductProjection. In case the referenced object does not exist, the API returns the non-expanded reference.

staged
Boolean

Set to true to retrieve the staged Product Projection

priceCurrency

The currency used for Price selection.

Pattern: ^[A-Z]{3}$
priceCountry

The country used for Price selection. Can only be used in conjunction with the priceCurrency parameter.

Pattern: ^[A-Z]{2}$
priceCustomerGroup
String

id of an existing CustomerGroup used for Price selection. Can only be used in conjunction with the priceCurrency parameter.

priceChannel
String

id of an existing Channel used for Price selection. Can only be used in conjunction with the priceCurrency parameter.

localeProjection
Array of Locale
storeProjection
String

key of an existing Store. If the Store has defined some languages, countries, distribution or supply Channels, they are used for projections based on locale, price, and inventory. If the Store has defined Product Selections, they have no effect on the results of this query.

ProductSearchFacetExpression

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

ProductPagedSearchResponse

total
Int

Total number of results matching the query.

offset
Int

Number of elements skipped.

Maximum: 9 900
limit
Int

Number of results requested.

Maximum: 100
facets

Results for facets when requested.

results

Search result containing the Products matching the search query.

ProductSearchResult

id
String

Unique identifier of the Product.

productProjection

Contains Product Projection data for Products matching the projection field in the Search Products request.

matchingVariants

Describes the variants that matched the search criteria.

ProductSearchMatchingVariants

allMatched
Boolean

Whether the search criteria definitely matches all Variants of the returned Product, like for Product-level fields. Is always false for search expressions on Variant-level fields.

matchedVariants

The variants matching the search criteria or empty if all matched.

ProductSearchMatchingVariantEntry

id
Int

Unique identifier of the variant.

sku
String

SKU of the matching variant.

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.

Contains results of the facet.

ProductSearchFacetResultBucketEntry

key
String

Key of the bucket.

count
Int

Number of values in the bucket.

Searchable Product fields

The following list contains the Product fields that can be used for the field property in query expressions. For standard fields on Products, the type of field determines which query expressions are supported for the field.

Attributes

The SearchFieldType of the Attribute determines in which query expressions you can use the Attribute specified by:

variants.attributes.<attribute-name>

To query for multiple values of the same field or for several Attributes in the same request, combine the query 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 query expressions.

Standard fieldQuery for
hasStagedChangesProducts that have changes on their staged representation compared to their current representation.
publishedProducts that are currently published.
variants.availability.isOnStockProductVariants that are available in stock.

Number and date fields

Use the following fields in exact, exists, and range query expressions. 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 query expressions. To query for multiple values of the same field or for several fields in the same request, combine the query 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.
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 specifc 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.
productSelectionsa list of all Product Selections identified by the id that a Product is assigned to.
storesa list of all Stores identified by the id that a Product is available in.

Text and localized text fields

Use the following localizedText type fields in exact, fullText, prefix, wildcard, and exists query 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
}
}
]
}
}

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

Definition of the distinct facet.

ProductSearchFacetDistinctValue

name
String

Name of the distinct facet to appear in the ProductSearchFacetResultBucket.

Whether the facet must consider only the Products resulting from the search (query) or all the Products (all).

Default: query
filter

Additional filtering expression to apply to the search 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.

Define how the buckets are sorted.

limit
Int

Maximum number of buckets to return.

Default: 200Maximum: 200
language

String value specifying linguistic and regional preferences using the IETF language tag format, as described in BCP 47. The format combines language, script, and region using hyphen-separated subtags. For example: en, en-US, zh-Hans-SG.

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

Definition of the ranges facet.

ProductSearchFacetRangesValue

name
String

Name of the ranges facet to appear in the ProductSearchFacetResultBucket.

Whether the facet must consider only the Products resulting from the search (query) or all the Products (all).

Default: query
filter

Additional filtering expression to apply to the search 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.

Define ranges for the facet.

language

String value specifying linguistic and regional preferences using the IETF language tag format, as described in BCP 47. The format combines language, script, and region using hyphen-separated subtags. For example: en, en-US, zh-Hans-SG.

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

Definition of the count facet.

ProductSearchFacetCountValue

name
String

Name of the count facet to appear in the ProductSearchFacetResultCount.

Whether the facet must consider only the Products resulting from the search (query) or all the Products (all).

Default: query
filter

Additional filtering expression to apply to the search 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
}
]
}
}

Operations on facets

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

Selecting 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
}
]
}
]
}
}

Filtering buckets by the beginning of the key

The optional field startsWith allows to filter bucket keys in the facets results by the beginning of the key. It also allows to specify case sensitivity for the match.

Example:

The following example filters buckets to match Attribute names that start with a case-insensitive "my":

Requestjson
{
"facets": [
{
"distinct": {
"name": "attributes",
"field": "attributes.name",
"startsWith": {
"value": "my",
"caseInsensitive": true
}
}
}
]
}
Responsejson
{
"facets": {
"results": [
{
"name": "attributes",
"buckets": [
{
"key": "My attribute 1",
"count": 1
},
{
"key": "my attribute 2",
"count": 2
}
]
}
]
}
}

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

Sorting 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"
}
}
]

Scoping 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
}
}
}
}
]
}

Product indexing

After the Product Search feature is activated, Products are indexed every 15 minutes.

  • Only the first 50 Attributes (following the order defined in the Product Type) of each Product Variant are indexed.
  • Any field with more than 8191 characters will not be indexed. Non-indexed fields are not searchable by default.

Prices

Prices are indexed in the variants.prices field, and they can be used for sorting, filtering and faceting.

The content of the variants.prices field is determined by the Product's priceMode. When the priceMode is set to Standalone, the variants.prices field contains Standalone prices. Otherwise, it contains Embedded prices.

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.

If Price selection parameters are provided in the ProductSearchProjectionParams, the price field in the returned Product Projections is determined by Price selection.

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

Localized content

To support exact, full text, prefix, and wildcard query 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