# Product Search Product Search enables you to create storefront discovery experiences for your customers where they can browse and search across your Products. Product Search supports searching through Products across [Stores](https://docs.commercetools.com/api/projects/stores.md#store) (up to 15000 per Product), [Product Selections](https://docs.commercetools.com/api/projects/product-selections.md#productselection) (up to 15000 per Product), and [Standalone Prices](https://docs.commercetools.com/api/projects/standalone-prices.md#standaloneprice) (up to 10000 per Product).\ These limits are soft. If you exceed the limits for Stores or Product Selections, the API returns non-deterministic results. For a large number of Standalone Prices, the API applies some [sorting algorithm](https://docs.commercetools.com/api/projects/product-search.md#large-number-of-standalone-prices) during product indexing to offer a balanced selection of prices. The Product Search API accepts queries expressed in the [search query language](https://docs.commercetools.com/api/search-query-language.md). The API supports only those [Locales](https://docs.commercetools.com/api/types.md#locale) configured in the [Project](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AProject). Queries for Locales not configured in the Project do not return any result. Product Search does not support retrieving [tailored product data](https://docs.commercetools.com/api/projects/product-tailoring.md). For such a scenario, use the [Get ProductProjection in Store](https://docs.commercetools.com/api/projects/productProjections.md#get-productprojection-in-store) endpoints. Only the current representation of [Products](https://docs.commercetools.com/api/projects/products.md#product) are indexed for Product Search. For performance reasons, there are some [limitations](https://docs.commercetools.com/api/limits.md#product-search) that are imposed by the API. Follow our [performance tips](https://docs.commercetools.com/api/performance-tips.md#product-search) to minimize the response times for the Product Search API. When you access Product Search with an [external OAuth token](https://docs.commercetools.com/api/authorization.md#request-an-access-token-using-an-external-oauth-server), include `view_products:{projectKey}` explicitly in the token scope. Don't rely on implied permissions when defining the token scope. Learn more about how to implement a product listing page (PLP) using Product Search in our self-paced Build product listing pages module. ### Compare product retrieval methods You can use three methods for retrieving product data, each suited to different use cases. Product Search is optimized for product discovery based on indexed product information. It allows full-text and non-exact searches using prefixes and wildcards, and provides faceting to support multi-facet navigation on product listing pages. Be aware of the [indexing delay](https://docs.commercetools.com/api/projects/product-search.md#product-indexing): the product information might not always reflect the latest state. [Query Product Projections](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Aendpoint%3A%2F%7BprojectKey%7D%2Fproduct-projections%3AGET) queries the product database directly to check Products against the provided predicates. Typical storefront queries can take significantly longer compared to a Product Search request. Use this method only when real-time data accuracy is more critical than query performance. Since this method returns either the current or the staged representation of a Product, the response is smaller than querying the full [Product](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AProduct) resource. Do not use this method to retrieve current product stock levels. The `availability` of a ProductVariant in a Product Projection is [eventually consistent](https://docs.commercetools.com/api/general-concepts.md#eventual-consistency) and may not reflect immediate stock. Instead, use the [Inventory API](https://docs.commercetools.com/api/projects/inventory.md). [Query Products](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Aendpoint%3A%2F%7BprojectKey%7D%2Fproducts%3AGET) returns entire [Products](https://docs.commercetools.com/api/projects/products.md#product) with both `current` and `staged` representations. Use this method only for product data management in backoffice applications. ## 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 You can fetch Product data for the matching Products by using the `product` field in the `results` of the `productsSearch` query. The following example also shows how [price selection](https://docs.commercetools.com/api/pricing-and-discounts-overview.md#price-selection) is applied for specific currency and country. ```graphql title="Example query to fetch Product data for matching Products" { productsSearch( query: {fullText: {field: "name", value: "Skirt", language: "en"}} ) { results { product { masterData{ current{ masterVariant{ sku images{ url } price ( currency: "USD" country: "US" ) { value { centAmount } } attributesRaw(includeNames: "designer"){ name value } } } } } } offset limit total } } ``` Find more examples for data integration with GraphQL in the [GraphQL API documentation](https://docs.commercetools.com/api/graphql.md#use-product-search). ### With Product Projection API Alternatively, you can use the [Product Projection API](https://docs.commercetools.com/api/projects/productProjections.md) to fetch the full representation of the Product Projections in the search result. Use the [Query ProductProjections](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Aendpoint%3A%2F%7BprojectKey%7D%2Fproduct-projections%3AGET) endpoint with a filter for the IDs that Product Search returns: ```http title="Example query predicate with ID filter" 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](https://docs.commercetools.com/api/projects/productProjections.md#projection-by-price-selection) returned. - `localeProjection` to limit the Locales returned for [localized fields](https://docs.commercetools.com/api/projects/productProjections.md#locales). - `filter[attributes]` to include only [specific Attributes](https://docs.commercetools.com/api/projects/productProjections.md#attributes). - `storeProjection` to include only the product availability for the specific Store and also the product data tailored for that Store. The Product Projections that this query returns are not guaranteed to be in the same order as the Product IDs that Product Search returns. To maintain the order, apply the same sorting logic on the Query ProductProjections endpoint as you used in the Product Search request. You can also fetch each Product Projection individually using the [Get ProductProjection by ID](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Aendpoint%3A%2F%7BprojectKey%7D%2Fproduct-projections%2F%7Bid%7D%3AGET) 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](https://docs.commercetools.com/api/deprecations-and-removals.md#productsearchprojectionparams) has been deprecated and will be removed in a future release. Use 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: - using the [Merchant Center](https://docs.commercetools.com/docs/login.md) by navigating to Settings > Project settings > Storefront Search. - using the [Change Product Search Indexing Enabled](https://docs.commercetools.com/api/projects/project.md#change-product-search-indexing-enabled) update action with `mode: ProductsSearch` on the [Update Project](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Aendpoint%3A%2F%7BprojectKey%7D%3APOST) endpoint. ```json { "action": "changeProductSearchIndexingEnabled", "enabled": true, "mode": "ProductsSearch" } ``` Once activated for your Project, your [Products get indexed](https://docs.commercetools.com/api/projects/product-search.md#product-indexing), and the [Search Products](https://docs.commercetools.com/api/projects/product-search.md#search-products) endpoint will become fully functional soon after. ### Automatic deactivation The Product Search API is automatically deactivated for a Project if there have been no calls to the [Search Products](https://docs.commercetools.com/api/projects/product-search.md#search-products) endpoint for 30 consecutive days. Once deactivated, the Product Search API rejects all search requests with an [ObjectNotFound](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AObjectNotFoundError) error. ```json { "statusCode": 400, "message": "Product Search API is not enabled for \"project-key\", if you intend to use it please enable indexing for Product Search API", "errors": [ { "code": "ObjectNotFound", "message": "Product Search API is not enabled for \"project-key\", if you intend to use it please enable indexing for Product Search API" } ] } ``` You can reactivate the Product Search API as described above. ## Representations #### ProductSearchRequest See API type definition [ProductSearchRequest](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchRequest). ##### ProductSearchFacetExpression Refer to expression for [distinct](https://docs.commercetools.com/api/projects/product-search.md#productsearchfacetdistinctexpression), [ranges](https://docs.commercetools.com/api/projects/product-search.md#productsearchfacetrangesexpression), [count](https://docs.commercetools.com/api/projects/product-search.md#productsearchfacetcountexpression), or [stats](https://docs.commercetools.com/api/projects/product-search.md#productsearchfacetstatsexpression) facets. #### ProductPagedSearchResponse See API type definition [ProductPagedSearchResponse](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductPagedSearchResponse). #### ProductSearchResult See API type definition [ProductSearchResult](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchResult). ##### ProductSearchMatchingVariants See API type definition [ProductSearchMatchingVariants](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchMatchingVariants). ##### ProductSearchMatchingVariantEntry See API type definition [ProductSearchMatchingVariantEntry](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchMatchingVariantEntry). #### ProductSearchFacetResult ##### ProductSearchFacetResultCount See API type definition [ProductSearchFacetResultCount](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetResultCount). ##### ProductSearchFacetResultBucket See API type definition [ProductSearchFacetResultBucket](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetResultBucket). ##### ProductSearchFacetResultBucketEntry See API type definition [ProductSearchFacetResultBucketEntry](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetResultBucketEntry). ##### ProductSearchFacetResultStats See API type definition [ProductSearchFacetResultStats](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetResultStats). ## Search Products See API endpoint definition [POST /\{projectKey}/products/search](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Aendpoint%3A%2F%7BprojectKey%7D%2Fproducts%2Fsearch%3APOST). ## Searchable Product fields This section lists the fields of the Product search index that can be used for the `field` property in [simple expressions](https://docs.commercetools.com/api/search-query-language.md#simple-expressions). For standard fields on Products, the [type of field](https://docs.commercetools.com/api/search-query-language.md#types-of-fields) determines which [simple expressions are supported](https://docs.commercetools.com/api/search-query-language.md#simple-expression-supported-for-which-type-of-field) 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](https://docs.commercetools.com/api/pricing-and-discounts-overview.md#embedded-prices) or [Standalone Prices](https://docs.commercetools.com/api/pricing-and-discounts-overview.md#standalone-prices), depending on the [Product's](https://docs.commercetools.com/api/projects/products.md#product) `priceMode`. ### Attributes The [SearchFieldType](https://docs.commercetools.com/api/search-query-language.md#searchfieldtype) of the Attribute determines the [simple expressions](https://docs.commercetools.com/api/search-query-language.md#simple-expressions) in which you can use the Attribute specified by: - `attributes.` (for Product Attributes) - `variants.attributes.` (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](https://docs.commercetools.com/api/search-query-language.md#compound-expressions). An Attribute must be declared as searchable in its [AttributeDefinition](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AAttributeDefinition). ### Boolean fields Use the following boolean [type fields](https://docs.commercetools.com/api/search-query-language.md#types-of-fields) on Products in [exact](https://docs.commercetools.com/api/search-query-language.md#exact) and [exists](https://docs.commercetools.com/api/search-query-language.md#exists) expressions. (a CSV formatted table follows. The first line are the column names.) Standard field,Query for `variants.availability.isOnStock`,[ProductVariants](https://docs.commercetools.com/api/projects/products.md#productvariant) that are [available in stock](https://docs.commercetools.com/api/projects/products.md#productvariantavailability) based on an [InventoryEntry](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AInventoryEntry) that has no assigned supply [Channel](https://docs.commercetools.com/api/projects/channels.md). `variants.prices.discounted`,[ProductVariants](https://docs.commercetools.com/api/projects/products.md#productvariant) with a discounted [Price](https://docs.commercetools.com/api/types.md#price). ### Number and date fields Use the following fields in [exact](https://docs.commercetools.com/api/search-query-language.md#exact), [exists](https://docs.commercetools.com/api/search-query-language.md#exists), and [range](https://docs.commercetools.com/api/search-query-language.md#range) expressions. You can request [ranges](https://docs.commercetools.com/api/projects/product-search.md#ranges-facets) and [stats](https://docs.commercetools.com/api/projects/product-search.md#stats-facets) facets for fields of this type. Data type indicates the [type of field](https://docs.commercetools.com/api/search-query-language.md#types-of-fields) for the field. (a CSV formatted table follows. The first line are the column names.) Standard field,Data type,Query for `reviewRatingStatistics.averageRating`,double,[Products](https://docs.commercetools.com/api/projects/products.md#product) with a certain average [review rating](https://docs.commercetools.com/api/projects/reviews.md#reviewratingstatistics). `reviewRatingStatistics.highestRating`,double,Products with a certain maximum in [review rating](https://docs.commercetools.com/api/projects/reviews.md#reviewratingstatistics). `reviewRatingStatistics.lowestRating`,double,Products with a certain minimum in [review rating](https://docs.commercetools.com/api/projects/reviews.md#reviewratingstatistics). `reviewRatingStatistics.count`,long,Products with a certain number of [reviews](https://docs.commercetools.com/api/projects/reviews.md#reviewratingstatistics). `variants.prices.currentCentAmount`,long,[ProductVariants](https://docs.commercetools.com/api/projects/products.md#productvariant) with a [Price](https://docs.commercetools.com/api/types.md#price) of certain cent amount taking the `discounted` price into account. `variants.prices.centAmount`,long,ProductVariants with a Price of certain cent amount not taking the `discounted` price into account. `variants.availability.availableQuantity`,long,ProductVariants with a certain [available quantity](https://docs.commercetools.com/api/projects/products.md#productvariantavailability). `variants.prices.validFrom`,dateTime,ProductVariants with a Price valid from a certain date and time. `variants.prices.validUntil`,dateTime,ProductVariants with a Price valid until a certain date and time. `createdAt`,dateTime,Products created at a certain date and time. `lastModifiedAt`,dateTime,Products last modified at any of its fields at a certain date and time. ### Keyword fields Use the following keyword [type fields](https://docs.commercetools.com/api/search-query-language.md#types-of-fields), containing unique IDs or keys, in [exact](https://docs.commercetools.com/api/search-query-language.md#exact), [prefix](https://docs.commercetools.com/api/search-query-language.md#prefix), [wildcard](https://docs.commercetools.com/api/search-query-language.md#wildcard), and [exists](https://docs.commercetools.com/api/search-query-language.md#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](https://docs.commercetools.com/api/search-query-language.md#compound-expressions). (a CSV formatted table follows. The first line are the column names.) Standard field,Query for `id`,a [Product](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AProduct) with a specific `id`. `key`,a Product with a specific `key`. `productType`,a Product of a specific [ProductType](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AProductType) identified by its `id`. `taxCategory`,a Product with a specific [TaxCategory](https://docs.commercetools.com/api/projects/taxCategories.md) identified by its `id`. `state`,a Product with a specific [State](https://docs.commercetools.com/api/projects/states.md) identified by its `id`. `categories`,a Product that has a specific [Category](https://docs.commercetools.com/api/projects/categories.md), 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](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3ACategoryOrderHints) identified by its Category `id`. `categoriesSubTree`,a Product that has a specific Category, identified by its `id`, or one of its subcategories assigned. `variants.id`,[ProductVariants](https://docs.commercetools.com/api/projects/products.md#productvariant) with a specific `id`. `variants.key`,a ProductVariant with a specific `key`. `variants.sku`,a ProductVariant with a specific `sku`. `variants.prices.id`,ProductVariants with a specific [Price](https://docs.commercetools.com/api/types.md#price) identified by its `id`. `variants.prices.currencyCode`,ProductVariants with prices for a specific currency formatted as [CurrencyCode](https://docs.commercetools.com/api/types.md#currencycode). `variants.prices.country`,ProductVariants with prices for a specific country formatted as [CountryCode](https://docs.commercetools.com/api/types.md#countrycode). `variants.prices.customerGroup`,ProductVariants with a Price for a specific [CustomerGroup](https://docs.commercetools.com/api/projects/customerGroups.md) identified by its `id`. `variants.prices.channel`,ProductVariants with a Price for a specific [Channel](https://docs.commercetools.com/api/projects/channels.md) identified by its `id`. `variants.availability.isOnStockForChannel`,ProductVariants that are available in stock for a specific [Channel](https://docs.commercetools.com/api/projects/channels.md) identified by its `id`. `variants.availability.hasInventoryEntryForChannel`,ProductVariants that have an [InventoryEntry](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AInventoryEntry) for a specific [Channel](https://docs.commercetools.com/api/projects/channels.md) identified by its `id`, regardless of whether they are available in stock. `variants.productSelections`,a [Product Selection](https://docs.commercetools.com/api/projects/product-selections.md#productvariantselection) identified by its `id` that a Product Variant is assigned to. `variants.stores`,a [Store](https://docs.commercetools.com/api/projects/stores.md) identified by its `id` that a Product Variant is [available in](https://docs.commercetools.com/api/project-configuration-overview.md#products-available-in-store). `productSelections`,a [Product Selection](https://docs.commercetools.com/api/projects/product-selections.md) identified by its `id` that a Product is assigned to. `stores`,a [Store](https://docs.commercetools.com/api/projects/stores.md) identified by its `id` that a Product is [available in](https://docs.commercetools.com/api/project-configuration-overview.md#products-available-in-store). ### Text and localized text fields Use the following localizedText [type fields](https://docs.commercetools.com/api/search-query-language.md#types-of-fields) in [exact](https://docs.commercetools.com/api/search-query-language.md#exact), [fullText](https://docs.commercetools.com/api/search-query-language.md#fulltext), [prefix](https://docs.commercetools.com/api/search-query-language.md#prefix), [fuzzy](https://docs.commercetools.com/api/search-query-language.md#fuzzy), [wildcard](https://docs.commercetools.com/api/search-query-language.md#wildcard), and [exists](https://docs.commercetools.com/api/search-query-language.md#exists) expressions. (a CSV formatted table follows. The first line are the column names.) Standard field,Data type,Query for `name`,localizedText,[Products](https://docs.commercetools.com/api/projects/products.md#product) with a specific `name` for a specific [Locale](https://docs.commercetools.com/api/types.md#locale). `slug`,localizedText,a [ProductVariant](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AProductVariant) with a specific `slug` for a specific [Locale](https://docs.commercetools.com/api/types.md#locale). `description`,localizedText,Products with a specific `description` for a specific [Locale](https://docs.commercetools.com/api/types.md#locale). `searchKeywords`,localizedText,Products with specific `searchKeywords` for a specific [Locale](https://docs.commercetools.com/api/types.md#locale). Example query: The following example demonstrates how to find Product Variants with a price of EUR 22.22: ```json title="Request" { "query": { "and": [ { "exact": { "field": "variants.prices.currencyCode", "value": "EUR" } }, { "exact": { "field": "variants.prices.centAmount", "value": 2222 } } ] } } ``` ## Filter examples The following examples show how to filter Products using the [search query language](https://docs.commercetools.com/api/search-query-language.md). For each example, you can find the REST API request body (json) and the equivalent GraphQL query (graphql). Use the `query` field in the [ProductSearchRequest](https://docs.commercetools.com/api/projects/product-search.md#productsearchrequest) to filter Products before [facets](https://docs.commercetools.com/api/projects/product-search.md#facets) are calculated. Use the `postFilter` field to filter Products after facets are calculated, which is useful for multi-facet navigation where facet counts should remain unaffected by applied filters. ### Filter by Category Use an [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expression on the `categories` [keyword field](https://docs.commercetools.com/api/projects/product-search.md#keyword-fields) to match Products assigned to a specific [Category](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3ACategory). ```json { "query": { "exact": { "field": "categories", "value": "{{category-id}}" } } } ``` ```graphql { productsSearch( query: { exact: { field: "categories", value: "{{category-id}}" } } ) { total results { id } } } ``` To check whether a Product has any Category assigned, use an [exists](https://docs.commercetools.com/api/search-query-language.md#exists) expression. ```json { "query": { "exists": { "field": "categories" } } } ``` ```graphql { productsSearch( query: { exists: { field: "categories" } } ) { total results { id } } } ``` To check whether a Product has no Category assigned, wrap an [exists](https://docs.commercetools.com/api/search-query-language.md#exists) expression in a [NOT expression](https://docs.commercetools.com/api/search-query-language.md#not-expression). ```json { "query": { "not": [ { "exists": { "field": "categories" } } ] } } ``` ```graphql { productsSearch( query: { not: [ { exists: { field: "categories" } } ] } ) { total results { id } } } ``` ### Filter by Category subtree Use an [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expression on the `categoriesSubTree` [keyword field](https://docs.commercetools.com/api/projects/product-search.md#keyword-fields) to match Products assigned to a specific [Category](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3ACategory) or any of its descendant Categories. ```json { "query": { "exact": { "field": "categoriesSubTree", "value": "{{category-id}}" } } } ``` ```graphql { productsSearch( query: { exact: { field: "categoriesSubTree", value: "{{category-id}}" } } ) { total results { id } } } ``` To filter by multiple Category subtrees (OR logic) with the REST API, specify several category IDs in the `values` array in one [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expression. ```json title="Products in one of multiple Category subtrees with REST API" { "query": { "exact": { "field": "categoriesSubTree", "values": ["{{category-id-1}}", "{{category-id-2}}"] } } } ``` The GraphQL `exact` input type has no `values` field. For the GraphQL API, combine multiple [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expressions with a single `value` in an [OR expression](https://docs.commercetools.com/api/search-query-language.md#or-expression) to achieve the same result. ```graphql title="Products in one of multiple Category subtrees with GraphQL" { productsSearch( query: { or: [ { exact: { field: "categoriesSubTree", value: "{{category-id-1}}" } } { exact: { field: "categoriesSubTree", value: "{{category-id-2}}" } } ] } ) { total results { id } } } ``` ### Filter by Price Depending on the Product's `priceMode`, this filter checks either [Embedded Prices](https://docs.commercetools.com/api/pricing-and-discounts-overview.md#embedded-prices) or [Standalone Prices](https://docs.commercetools.com/api/pricing-and-discounts-overview.md#standalone-prices). Use an [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expression for an exact price match, or a [range](https://docs.commercetools.com/api/search-query-language.md#range) expression for a price range. Filter on `variants.prices.currentCentAmount` if matching prices can be affected by Product Discounts. ```json { "query": { "exact": { "field": "variants.prices.currentCentAmount", "value": 9999 } } } ``` ```graphql { productsSearch( query: { exact: { field: "variants.prices.currentCentAmount", value: 9999 } } ) { total results { id } } } ``` Filter on `variants.prices.centAmount` if only the original prices should be considered, not those reduced by Product Discounts. ```json { "query": { "range": { "field": "variants.prices.centAmount", "gte": 1000, "lte": 5000 } } } ``` ```graphql { productsSearch( query: { range: { long: { field: "variants.prices.centAmount", gte: 1000, lte: 5000 } } } ) { total results { id } } } ``` To filter for Products with a discounted price, use an [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expression on the `variants.prices.discounted` [boolean field](https://docs.commercetools.com/api/projects/product-search.md#boolean-fields). ```json { "query": { "exact": { "field": "variants.prices.discounted", "value": true } } } ``` ```graphql { productsSearch( query: { exact: { field: "variants.prices.discounted", value: true } } ) { total results { id } } } ``` To check whether a Product Variant has any Price, use an [exists](https://docs.commercetools.com/api/search-query-language.md#exists) expression on `variants.prices.centAmount`. ```json { "query": { "exists": { "field": "variants.prices.centAmount" } } } ``` ```graphql { productsSearch( query: { exists: { field: "variants.prices.centAmount" } } ) { total results { id } } } ``` Checking the non-existence of `variants.prices` using `not` + `exists` is not supported and always returns an empty result. See [Evaluation of 'not' expressions](https://docs.commercetools.com/api/projects/product-search.md#evaluation-of-not-expressions). To find Products with prices that are the most suitable for your customer, filter by the price scopes that apply and combine them with an [AND expression](https://docs.commercetools.com/api/search-query-language.md#and-expression). The following scopes are available for filtering: - `variants.prices.currencyCode`: The currency of the price, formatted as [CurrencyCode](https://docs.commercetools.com/api/types.md#currencycode). - `variants.prices.country`: The country of the price, formatted as [CountryCode](https://docs.commercetools.com/api/types.md#countrycode). - `variants.prices.customerGroup`: The Customer Group of the price, identified by its `id`. - `variants.prices.channel`: The distribution channel of the price, identified by its `id`. The following example shows how to filter Products by a specific [Channel](https://docs.commercetools.com/api/projects/channels.md), currency, and country, that are in a specific price range. Channel and currency are fixed to one value, while country can be one of several values. For the REST API, this is done by combining [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expressions for `variants.prices.channel`, `variants.prices.country`, and `variants.prices.currencyCode` with a [range](https://docs.commercetools.com/api/search-query-language.md#range) expression for `variants.prices.currentCentAmount` in an [AND expression](https://docs.commercetools.com/api/search-query-language.md#and-expression). ```json title="Products by Price scopes with a value in a range using REST API" { "query": { "and": [ { "exact": { "field": "variants.prices.channel", "value": "{{channel-id}}" } }, { "exact": { "field": "variants.prices.country", "values": ["BE", "LU", "NL"] } }, { "exact": { "field": "variants.prices.currencyCode", "value": "EUR" } }, { "range": { "field": "variants.prices.currentCentAmount", "gte": 1000, "lte": 5000 } } ] } } ``` For the GraphQL API, the implicit OR logic for the multiple values of the `variants.prices.country` field is not supported, so you need an [OR](https://docs.commercetools.com/api/search-query-language.md#or-expression) compound expression for the country values in addition. ```graphql title="Products by Price scopes with a value in a range using GraphQL" { productsSearch( query: { and: [ { exact: { field: "variants.prices.channel", value: "{{channel-id}}" } } { or: [ { exact: { field: "variants.prices.country", value: "BE" } } { exact: { field: "variants.prices.country", value: "LU" } } { exact: { field: "variants.prices.country", value: "NL" } } ] } { exact: { field: "variants.prices.currencyCode", value: "EUR" } } { range: { long: { field: "variants.prices.currentCentAmount", gte: 1000, lte: 5000 } } } ] } ) { total results { id } } } ``` You can also use the `variants.prices.validFrom` and `variants.prices.validUntil` fields to filter for prices that are valid at a specific date and time. ```json { "query": { "and": [ { "exact": { "field": "variants.prices.currencyCode", "value": "EUR" } }, { "range": { "field": "variants.prices.currentCentAmount", "gte": 1000, "lte": 5000 } }, { "range": { "field": "variants.prices.validFrom", "lte": "{{date-time}}" } }, { "range": { "field": "variants.prices.validUntil", "gte": "{{date-time}}" } } ] } } ``` ```graphql { productsSearch( query: { and: [ { exact: { field: "variants.prices.channel", value: "{{channel-id}}" } } { exact: { field: "variants.prices.currencyCode", value: "EUR" } } { range: { long: { field: "variants.prices.currentCentAmount", gte: 1000, lte: 5000 } } } { range: { datetime: { field: "variants.prices.validFrom", lte: "{{date-time}}" } } } { range: { datetime: { field: "variants.prices.validUntil", gte: "{{date-time}}" } } } ] } ) { total results { id } } } ``` You can add more expressions to this filter, for example for a discounted price, by adding an [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expression for `variants.prices.discounted` with the value `true` to the [AND expression](https://docs.commercetools.com/api/search-query-language.md#and-expression). ### Filter by Attribute values To filter by an Attribute value, include the `fieldType` property in the query expression. The `fieldType` value must match the [SearchFieldType](https://docs.commercetools.com/api/search-query-language.md#searchfieldtype) of the Attribute. An Attribute must be declared as searchable in its [AttributeDefinition](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AAttributeDefinition) to be available for filtering. The `fieldType` value and the `field` path depend on the Attribute type, as shown in the following table. (a CSV formatted table follows. The first line are the column names.) Attribute type,fieldType value,Field path [Text](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AAttributeTextType) and [Localized Text](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AAttributeLocalizableTextType),`text` and `ltext`,`variants.attributes.{name}` [Enum](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AAttributeEnumType) and [Localized Enum](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AAttributeLocalizedEnumType),`enum` and `lenum`,`variants.attributes.{name}.key` [Money](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AAttributeMoneyType),`money`,`variants.attributes.{name}.centAmount` or `variants.attributes.{name}.currencyCode` [Reference](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AAttributeReferenceType),`reference`,`variants.attributes.{name}.id` [Set](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AAttributeSetType),`set_*`, matching the element type, for example `set_text`,`variants.attributes.{name}`, or `variants.attributes.{name}.key` for a Set of Enum or Set of Localized Enum values ```json { "query": { "exact": { "field": "variants.attributes.color", "fieldType": "text", "value": "red" } } } ``` ```graphql { productsSearch( query: { exact: { field: "variants.attributes.color", fieldType: text, value: "red" } } ) { total results { id } } } ``` To match Products where an Attribute has one of several values (OR logic), such as the checkboxes in a multi-select facet panel, use the `values` array in a single [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expression for the REST API. The GraphQL `exact` input type has no `values` field, so for the GraphQL API, combine multiple `exact` expressions with a single `value` in an [OR expression](https://docs.commercetools.com/api/search-query-language.md#or-expression) instead. ```json { "query": { "exact": { "field": "variants.attributes.size.key", "fieldType": "enum", "values": ["S", "M", "L"] } } } ``` ```graphql { productsSearch( query: { or: [ { exact: { field: "variants.attributes.size.key", fieldType: enum, value: "S" } } { exact: { field: "variants.attributes.size.key", fieldType: enum, value: "M" } } { exact: { field: "variants.attributes.size.key", fieldType: enum, value: "L" } } ] } ) { total results { id } } } ``` For a Set Attribute, match Products where the set contains all of the specified values (AND logic) with multiple [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expressions inside an [AND expression](https://docs.commercetools.com/api/search-query-language.md#and-expression). To match Products where the set contains any of the specified values (OR logic), use the `values` array in a single `exact` expression for the REST API, or combine multiple `exact` expressions in an [OR expression](https://docs.commercetools.com/api/search-query-language.md#or-expression) for the GraphQL API. ```json { "query": { "and": [ { "exact": { "field": "variants.attributes.features", "fieldType": "set_text", "value": "waterproof" } }, { "exact": { "field": "variants.attributes.features", "fieldType": "set_text", "value": "washable" } } ] } } ``` ```graphql { productsSearch( query: { and: [ { exact: { field: "variants.attributes.features", fieldType: set_text, value: "waterproof" } } { exact: { field: "variants.attributes.features", fieldType: set_text, value: "washable" } } ] } ) { total results { id } } } ``` ```json { "query": { "exact": { "field": "variants.attributes.features", "fieldType": "set_text", "values": ["waterproof", "washable"] } } } ``` ```graphql { productsSearch( query: { or: [ { exact: { field: "variants.attributes.features", fieldType: set_text, value: "waterproof" } } { exact: { field: "variants.attributes.features", fieldType: set_text, value: "washable" } } ] } ) { total results { id } } } ``` ### Filter by Variant availability Use an [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expression on the `variants.availability.isOnStock` [boolean field](https://docs.commercetools.com/api/projects/product-search.md#boolean-fields) to match Products that have at least one [ProductVariant](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AProductVariant) with inventory in stock, based on an [InventoryEntry](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AInventoryEntry) that has no assigned supply Channel. ```json { "query": { "exact": { "field": "variants.availability.isOnStock", "value": true } } } ``` ```graphql { productsSearch( query: { exact: { field: "variants.availability.isOnStock", value: true } } ) { total results { id } } } ``` Use an [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expression on `variants.availability.isOnStockForChannel` to match Products with inventory available in a specific supply [Channel](https://docs.commercetools.com/api/projects/channels.md). ```json { "query": { "exact": { "field": "variants.availability.isOnStockForChannel", "value": "{{channel-id}}" } } } ``` ```graphql { productsSearch( query: { exact: { field: "variants.availability.isOnStockForChannel", value: "{{channel-id}}" } } ) { total results { id } } } ``` Use a [range](https://docs.commercetools.com/api/search-query-language.md#range) expression on `variants.availability.availableQuantity` to filter by a range of available quantities. ```json { "query": { "range": { "field": "variants.availability.availableQuantity", "lt": 10 } } } ``` ```graphql { productsSearch( query: { range: { long: { field: "variants.availability.availableQuantity", lt: 10 } } } ) { total results { id } } } ``` ### Filter by review rating Filter Products by [ReviewRatingStatistics](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AReviewRatingStatistics) values using [exact](https://docs.commercetools.com/api/search-query-language.md#exact) or [range](https://docs.commercetools.com/api/search-query-language.md#range) expressions on the following [number and date fields](https://docs.commercetools.com/api/projects/product-search.md#number-and-date-fields): - `reviewRatingStatistics.averageRating` - `reviewRatingStatistics.highestRating` - `reviewRatingStatistics.lowestRating` - `reviewRatingStatistics.count` In GraphQL, use `float` as the sub-type for the rating fields and `long` for the `count` field in `range` expressions. ```json { "query": { "range": { "field": "reviewRatingStatistics.averageRating", "gte": 4.0, "lte": 5.0 } } } ``` ```graphql { productsSearch( query: { range: { float: { field: "reviewRatingStatistics.averageRating", gte: 4.0, lte: 5.0 } } } ) { total results { id } } } ``` ```json { "query": { "range": { "field": "reviewRatingStatistics.count", "gte": 10 } } } ``` ```graphql { productsSearch( query: { range: { long: { field: "reviewRatingStatistics.count", gte: 10 } } } ) { total results { id } } } ``` ### Filter by Store and Product Selection Unlike other searchable fields, `stores` and `productSelections` are not properties of the [Product](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AProduct) resource itself. They reflect a Product's Store and [Product Selection](https://docs.commercetools.com/api/projects/product-selections.md) assignments, which Product Search indexes separately at the [context level](https://docs.commercetools.com/api/projects/product-search.md#field-levels). Use an [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expression to match Products available in a specific [Store](https://docs.commercetools.com/api/projects/stores.md), or assigned to a specific Product Selection, identified by `id`. An [exists](https://docs.commercetools.com/api/search-query-language.md#exists) expression checks whether a Product is assigned to any Store or Product Selection at all. - `stores`: a Store a Product is available in. - `variants.stores`: a Store a [ProductVariant](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AProductVariant) is available in. - `productSelections`: a Product Selection a Product is assigned to. - `variants.productSelections`: a Product Selection a ProductVariant is assigned to. ```json { "query": { "exact": { "field": "stores", "value": "{{store-id}}" } } } ``` ```graphql { productsSearch( query: { exact: { field: "stores", value: "{{store-id}}" } } ) { total results { id } } } ``` To match Products available in any of several Stores, use the `values` array in the REST API, or combine multiple [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expressions in an [OR expression](https://docs.commercetools.com/api/search-query-language.md#or-expression) for the GraphQL API. The same pattern applies to `productSelections`, `variants.stores`, and `variants.productSelections`, by substituting the field name. ### Filter by SKU Filter Products by the `sku` of a [ProductVariant](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AProductVariant) using a [prefix](https://docs.commercetools.com/api/search-query-language.md#prefix) expression on the `variants.sku` field. ```json { "query": { "prefix": { "field": "variants.sku", "value": "{{sku}}" } } } ``` ```graphql { productsSearch( query: { prefix: { field: "variants.sku", value: "{{sku}}" } } ) { total results { id } } } ``` Checking the non-existence of `variants.sku` using [NOT](https://docs.commercetools.com/api/search-query-language.md#not-expression) combined with [exists](https://docs.commercetools.com/api/search-query-language.md#exists) is not supported and always returns an empty result. See [Evaluation of 'not' expressions](https://docs.commercetools.com/api/projects/product-search.md#evaluation-of-not-expressions). ### Filter by other Product fields This section shows how to filter Products by other fields that are not covered in the previous sections, such as `taxCategory`, `state`, `searchKeywords`, `createdAt`, and `lastModifiedAt`. #### Filter by TaxCategory The following example shows the pattern applied to the `taxCategory` field, for example, to filter for a reduced-rate [TaxCategory](https://docs.commercetools.com/api/projects/taxCategories.md). ```json { "query": { "exact": { "field": "taxCategory", "value": "{{tax-category-id}}" } } } ``` ```graphql { productsSearch( query: { exact: { field: "taxCategory", value: "{{tax-category-id}}" } } ) { total results { id } } } ``` ```json { "query": { "exists": { "field": "taxCategory" } } } ``` ```graphql { productsSearch( query: { exists: { field: "taxCategory" } } ) { total results { id } } } ``` ```json { "query": { "not": [ { "exists": { "field": "taxCategory" } } ] } } ``` ```graphql { productsSearch( query: { not: [ { exists: { field: "taxCategory" } } ] } ) { total results { id } } } ``` The same pattern applies to `productType` or other [keyword fields](https://docs.commercetools.com/api/projects/product-search.md#keyword-fields). Since every Product has a [ProductType](https://docs.commercetools.com/api/projects/productTypes.md), the [exists](https://docs.commercetools.com/api/search-query-language.md#exists) expression is not useful for filtering by `productType`. #### Filter by States Use the `values` array in the REST API, or combine multiple [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expressions in an [OR expression](https://docs.commercetools.com/api/search-query-language.md#or-expression) for the GraphQL API. ```json { "query": { "exact": { "field": "state", "values": ["{{state-id-1}}", "{{state-id-2}}"] } } } ``` ```graphql { productsSearch( query: { or: [ { exact: { field: "state", value: "{{state-id-1}}" } } { exact: { field: "state", value: "{{state-id-2}}" } } ] } ) { total results { id } } } ``` #### Filter by searchKeywords To filter by the `searchKeywords` [localized text field](https://docs.commercetools.com/api/projects/product-search.md#text-and-localized-text-fields), use an [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expression with the `language` property set to the language of the keyword. ```json { "query": { "exact": { "field": "searchKeywords", "language": "en", "value": "{{keyword}}" } } } ``` ```graphql { productsSearch( query: { exact: { field: "searchKeywords", language: "en", value: "{{keyword}}" } } ) { total results { id } } } ``` #### Filter by creation or modification date To filter Products by creation or modification date, use a [range](https://docs.commercetools.com/api/search-query-language.md#range) expression on the `createdAt` or `lastModifiedAt` [datetime fields](https://docs.commercetools.com/api/projects/product-search.md#number-and-date-fields). Omit the lower bound to retrieve Products created or modified before a certain date. Omit the upper bound to retrieve Products created or modified after a certain date. In GraphQL, use the `datetime` sub-type for range expressions on datetime fields. ```json { "query": { "range": { "field": "createdAt", "gte": "2025-01-01T00:00:00.000Z" } } } ``` ```graphql { productsSearch( query: { range: { datetime: { field: "createdAt", gte: "2025-01-01T00:00:00.000Z" } } } ) { total results { id } } } ``` ```json { "query": { "range": { "field": "lastModifiedAt", "gte": "2025-01-01T00:00:00.000Z", "lt": "2026-01-01T00:00:00.000Z" } } } ``` ```graphql { productsSearch( query: { range: { datetime: { field: "lastModifiedAt" gte: "2025-01-01T00:00:00.000Z" lt: "2026-01-01T00:00:00.000Z" } } } ) { total results { id } } } ``` ## 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](https://docs.commercetools.com/api/projects/product-search.md#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. The API returns all Products that have at least one Product Variant matching the query expression. To find out which of the Product Variants match the search query, we recommend setting `markMatchingVariants` to `true` in the [ProductSearchRequest](https://docs.commercetools.com/api/projects/product-search.md#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. ```json title="Returned Product matching price level field requested with markMatchingVariants: true" ... { "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. ```json title="Returned Product for which all Variants match, requested with markMatchingVariants: true" ... { "id": "{{ID-of-the-matching-Product}}", "matchingVariants": { "allMatched": true, "matchedVariants": [] } }, ... ``` ### Evaluation of 'not' expressions For a `not` [expression](https://docs.commercetools.com/api/search-query-language.md#compound-expressions), the API returns only the Products for which at least one Product Variant does not match the nested [simple expression(s)](https://docs.commercetools.com/api/search-query-language.md#simple-expressions). For example, given the following simplified product catalog: (a CSV formatted table follows. The first line are the column names.) Product,Variant,Price,Color Attribute Product\_1,1,30.00 USD,red ,2,30.00 EUR,yellow Product\_2,1,15.00 USD,yellow Product\_3,1,30.00 EUR,red The following query returns all Products that have a `red` variant where not all of the following price conditions are met: - currency is `USD` and - amount is `3000` cents. The sub-expressions inside the `not` expression are combined with an implicit `and`. ```json title="Request" { "query": { "and": [ { "exact": { "field": "variants.attributes.color", "fieldType": "text", "value": "red" } }, { "not": [ { "exact": { "field": "variants.prices.currencyCode", "value": "USD" } }, { "exact": { "field": "variants.prices.centAmount", "value": 3000 } } ] } ] }, "markMatchingVariants": true } ``` Since the product must be `red`, but the price must not be `30.00 USD`, the expression is true for `Product_3` only: (a CSV formatted table follows. The first line are the column names.) Product,Variant,Price,Color Attribute Product\_1,1,30.00 USD,red ,2,30.00 EUR,yellow Product\_2,1,15.00 USD,yellow Product\_3,1,30.00 EUR,red ```json title="Response containing only Product_3" { "total" : 1, "offset" : 0, "limit" : 20, "results" : [ { "id" : "Product_3", "matchingVariants" : { "allMatched" : false, "matchedVariants" : [ { "id" : 1 } ] } } ] } ``` The following query has the same price conditions as before (not `USD` and `3000` cents together), but now it returns Products that have a `yellow` variant: ```json title="Request" { "query": { "and": [ { "exact": { "field": "variants.attributes.color", "fieldType": "text", "value": "yellow" } }, { "not": [ { "exact": { "field": "variants.prices.currencyCode", "value": "USD" } }, { "exact": { "field": "variants.prices.centAmount", "value": 3000 } } ] } ] }, "markMatchingVariants": true } ``` The expression is true for variant `2` of `Product_1` and variant `1` of `Product_2`: (a CSV formatted table follows. The first line are the column names.) Product,Variant,Price,Color Attribute Product\_1,1,30.00 USD,red ,2,30.00 EUR,yellow Product\_2,1,15.00 USD,yellow Product\_3,1,30.00 EUR,red ```json title="Response containing Products 1 and 2" { "total" : 2, "offset" : 0, "limit" : 20, "facets" : [ ], "results" : [ { "id" : "Product_1", "matchingVariants" : { "allMatched" : false, "matchedVariants" : [ { "id" : 2 } ] } }, { "id" : "Product_2", "matchingVariants" : { "allMatched" : false, "matchedVariants" : [ { "id" : 1 } ] } } ] } ``` Not exists limitations Checking the non-existence of fields `variants` and `variants.prices` is not supported and always returns an empty result: ```json title="Not supported request" { "query": { "not": [ { "exists": { "field": "variants.prices" } } ] } } ``` ## Query validation During query validation, the API analyzes the search query and evaluates its expressions and the validity of its [searchable fields](https://docs.commercetools.com/api/projects/product-search.md#searchable-product-fields). ### Field levels For query validation, the API organizes the indexed product information into three different levels: (a CSV formatted table follows. The first line are the column names.) Field level,Searchable Product fields,Rank context level,`stores`, `productSelections`,1 product level,all Product fields, for example `key`, `name`, `productType`, all Product Variant fields (`variants.*`) except for prices,2 price level,all 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](https://docs.commercetools.com/api/projects/product-search.md#field-levels). 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: ```json title="Query template" "query": { "and" | "or": [ , , , ] } ``` Each criterion can be a [simple](https://docs.commercetools.com/api/search-query-language.md#simple-expressions) or [compound expression](https://docs.commercetools.com/api/search-query-language.md#compound-expressions) that applies only to searchable fields at that [field level](https://docs.commercetools.com/api/projects/product-search.md#field-levels). 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. ```bash title="Example compound expression" 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. ```mermaid graph TB PCO[variants.prices.country]-->OR1[OR]; PCH[variants.prices.channel]-->OR1 OR1[OR] -- 1. --> AND[AND] H[variants.attributes.color]-->OR2[OR] I[description]-->OR2 PCU[variants.prices.currencyCode] -- 2. --> AND OR2 -- 3. --> AND S[stores] -- 4. --> AND PS[productSelections] -- 5. --> AND ``` #### Valid compound expressions The API employs a hierarchical field level validation for [compound expressions](https://docs.commercetools.com/api/search-query-language.md#compound-expressions). Validation proceeds by first determining the [field level](https://docs.commercetools.com/api/projects/product-search.md#field-levels) among the constituent [simple expressions](https://docs.commercetools.com/api/search-query-language.md#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. ```mermaid graph TB CL1[context level 1] --> CC[context level 1] CL2[context level 1] --> CC PL1[product level 2] --> CP[product level 2] PL2[product level 2] --> CP PR1[price level 3] --> CPR[price level 3] PR2[price level 3] --> CPR ``` - 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. ```mermaid graph TB CL1[context level 1] --> CPO[context/product level 1] CL2[product level 2] --> CPO CL3[context level 1] --> CPR[context/price level 1] CL4[price level 3] --> CPR PL1[product level 2] --> CP[product/price level 2] PL2[price level 3] --> CP ``` - 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. ```mermaid graph TB CL1[context/product level 1] --> CC[context/product level 1] CL2[context level 1] --> CC CP1[context/price level 1] --> CCP[context/price level 1] CP2[context level 1] --> CCP PL1[product/price level 2] --> CP[product/price level 2] PL2[product level 2] --> CP ``` - 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. ```mermaid graph TB PL1[product/price level 2] --> CP[context/product/price level 1] PL2[context level 1] --> CP ``` 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](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AInvalidInputError) error. ```mermaid graph TB CL1[context/product level 1] --> CC[1 < 3 => Invalid!] CL2[price level 3] --> CC PL1[product/price level 2] --> CP[2 < 3 => Invalid!] PL2[price level 3] --> CP ``` - If both expressions are multi-level the query is invalid, regardless of the ranks, and the API responds with an [InvalidInput](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AInvalidInputError) error. ```mermaid graph TB PL1[product/price level 2] --> CP[Invalid!] CC[context/product level 1] --> CP ``` 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. ```bash title="Example error message - formatted for better analysis" 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](https://docs.commercetools.com/api/projects/product-search.md#distinct-facets), and the second one by a [ranges facet](https://docs.commercetools.com/api/projects/product-search.md#ranges-facets). Each facet has a `name` assigned to it, so it can be found in the [ProductSearchFacetResult](https://docs.commercetools.com/api/projects/product-search.md#productsearchfacetresult). If you only want to view the facets, set `limit` to `0` in the [ProductSearchRequest](https://docs.commercetools.com/api/projects/product-search.md#productsearchrequest). ### Distinct facets The distinct facet counts occurrences of distinct values. For [Set Type](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AAttributeSetType) Attributes, distinct facets count each element in the set as a value of the element type. For example, use `fieldType` `set_text` for a set of `text` Attributes. The returned buckets contain the individual text values. #### ProductSearchFacetDistinctExpression See API type definition [ProductSearchFacetDistinctExpression](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetDistinctExpression). #### ProductSearchFacetDistinctValue See API type definition [ProductSearchFacetDistinctValue](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetDistinctValue). 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`. ```json title="Request" { "facets": [ { "distinct": { "name": "sizes", "field": "variants.attributes.size", "fieldType": "number", "limit": 50 } } ] } ``` ```json title="Response" { "facets": [ { "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](https://docs.commercetools.com/api/types.md#datetime). You can specify ranges using `from` and `to`. Open ranges can be specified by omitting either the `from` or `to` values. Range facet buckets include the lower bound and exclude the upper bound. For example, with adjacent ranges `1` to `10` and `10` to `20`, a value of `10` is counted only in the second bucket. This behavior differs from [range filters](https://docs.commercetools.com/api/search-query-language.md#range), which can include both bounds when you use the `gte` and `lte` operators. #### ProductSearchFacetRangesExpression See API type definition [ProductSearchFacetRangesExpression](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetRangesExpression). #### ProductSearchFacetRangesValue See API type definition [ProductSearchFacetRangesValue](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetRangesValue). #### ProductSearchFacetRangesFacetRange See API type definition [ProductSearchFacetRangesFacetRange](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetRangesFacetRange). 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`. ```json title="Request" { "facets": [ { "ranges": { "name": "screenDiagonalRanges", "field": "variants.attributes.screenDiagonal", "fieldType": "number", "ranges": [ { "to": 40 }, { "from": 40, "to": 55 }, { "from": 55 } ] } } ] } ``` ```json title="Response" { "facets": [ { "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`. ```json title="Response" { "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 See API type definition [ProductSearchFacetCountExpression](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetCountExpression). #### ProductSearchFacetCountValue See API type definition [ProductSearchFacetCountValue](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetCountValue). #### ProductSearchFacetCountLevelEnum See API type definition [ProductSearchFacetCountLevelEnum](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetCountLevelEnum). 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). ```json title="Request" { "facets": [ { "count": { "name": "all-products" } }, { "count": { "name": "all-variants", "level": "variants" } } ] } ``` ```json title="Response" { "facets": [ { "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](https://docs.commercetools.com/api/projects/product-search.md#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 See API type definition [ProductSearchFacetStatsExpression](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetStatsExpression). #### ProductSearchFacetStatsValue See API type definition [ProductSearchFacetStatsValue](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetStatsValue). 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. ```json title="Request" { "facets": [ { "stats": { "name": "pricesStatsVariants", "field": "variants.prices.centAmount" } } ] } ``` ```json title="Response" { "facets": [ { "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. ```json title="Request" { "facets": [ { "stats": { "name": "pricesValidFromStats", "field": "variants.prices.validFrom" } } ] } ``` ```json title="Response" { "facets": [ { "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. ```json title="Request" { "facets": [ { "distinct": { "name": "categories", "field": "categories", "includes": [ "53992aac-cbbf-4b97-bb-09142435e1ea", "344c8ceb-01ac-4d76-8f93-bbbc3d0edd5d" ] } } ] } ``` ```json title="Response" { "facets": [ { "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](https://docs.commercetools.com/api/projects/states.md). 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. ```json title="Request" { "facets": [ { "distinct": { "name": "states", "field": "state", "missing": "N/A", "limit": 50 } } ] } ``` ```json title="Response" { "facets": [ { "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 See API type definition [ProductSearchFacetDistinctBucketSortExpression](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetDistinctBucketSortExpression). ##### ProductSearchFacetDistinctBucketSortBy See API type definition [ProductSearchFacetDistinctBucketSortBy](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetDistinctBucketSortBy). Example: The following example sorts an Attribute called "name" by `key` in descending order. ```json title="Request" { "facets": [ { "distinct": { "name": "attributes", "field": "variants.attributes.name", "sort": { "by": "key", "order": "desc" } } } ] } ``` ```json title="Response" { "facets": [ { "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). ```json title="Request" "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 See API type definition [ProductSearchFacetScopeEnum](https://docs.commercetools.com/api/projects/product-search.md?urn=ctp%3Aapi%3Atype%3AProductSearchFacetScopeEnum). ##### 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. ```json title="Request" { "query": { "fullText": { "field": "description", "value": "butter", "language": "en" } }, "facets": [ { "distinct": { "scope": "all", "name": "names", "field": "name", "language": "en", "limit": 50 } } ] } ``` ##### Filter facet To calculate facets on a specific subset of Products rather than the entire query result, use a `filter` facet. Example: In the following example, `androidTabletOSVersions` is scoped only to Products that have the Attribute `isTablet` set to `true`. ```json title="Request" { "query": { "exact": { "field": "variants.attributes.os", "fieldType": "text", "value": "Android" } }, "facets": [ { "distinct": { "scope": "query", "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. Sorting is specified using the `sort` array in the [ProductSearchRequest](https://docs.commercetools.com/api/projects/product-search.md#productsearchrequest). Each element in the array is a [SearchSorting](https://docs.commercetools.com/api/search-query-language.md#searchsorting) object with a `field`, `order` (`asc` or `desc`), and optionally a `mode`, `language`, `filter`, and `fieldType`. ### Sort by standard fields You can sort Products by the following standard fields: `name`, `createdAt`, `lastModifiedAt`, and `id`. To sort by `name`, specify the language using the `language` field in the sort object: ```json { ... "sort": [ { "field": "name", "language": "en", "order": "asc" } ] } ``` ```graphql { productsSearch( sort: [ { field: "name", language: "en", order: asc } ] ) { total results { id } } } ``` To sort by creation date or last modification date: ```json { ... "sort": [ { "field": "lastModifiedAt", "order": "desc" } ] } ``` ```graphql { productsSearch( sort: [ { field: "lastModifiedAt", order: desc } ] ) { total results { id } } } ``` To sort by `id`, which produces a stable, reproducible ordering independent of content: ```json { ... "sort": [ { "field": "id", "order": "asc" } ] } ``` ```graphql { productsSearch( sort: [ { field: "id", order: asc } ] ) { total results { id } } } ``` ### Sort by relevance score The `score` field sorts Products by their full-text search relevance score. When a full-text query is provided without an explicit sort, results are already sorted by `score` descending by default. Combine `score` with `id` as a tie-breaker to produce stable, reproducible result pages when multiple Products share the same relevance score: ```json { ... "sort": [ { "field": "score", "order": "desc" }, { "field": "id", "order": "asc" } ] } ``` ```graphql { productsSearch( sort: [ { field: "score", order: desc } { field: "id", order: asc } ] ) { total results { id } } } ``` ### 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: ```json { ... "sort": [ { "field": "variants.prices.centAmount", "order": "asc", "mode": "min" }, { "field": "score", "order": "desc" } ] } ``` ```graphql { productsSearch( sort: [ { field: "variants.prices.centAmount", order: asc, mode: min } { field: "score", order: desc } ] ) { total results { id } } } ``` To avoid non-deterministic ordering when Products share the same value for all sort fields, always end the sort array with a field that has a distinct value across all Products, such as `id`: ```json { ... "sort": [ { "field": "name", "language": "en", "order": "asc" }, { "field": "id", "order": "asc" } ] } ``` ```graphql { productsSearch( sort: [ { field: "name", language: "en", order: asc } { field: "id", order: asc } ] ) { total results { id } } } ``` ### Sort filters Sort filters cannot be used with [context level](https://docs.commercetools.com/api/projects/product-search.md#field-levels) fields. Sort filters can be used to sort returned Products by criteria that are specific to certain Product Variants. Sorting runs independently of the `query` and `postFilter` used to select Products, so it has no knowledge of those filters. If sorting needs to consider only a subset of Product Variants or Prices, that scoping must be expressed in the sort's own `filter`. For example, you can specify that only those variants with a [price scoped to a certain channel and currency](https://docs.commercetools.com/api/projects/product-search.md#sort-by-scoped-price) should be considered for sorting. In GraphQL, the sort `filter` field uses the same `SearchFilterExpressionInput` type as the `query` and `postFilter` arguments. This means the same [exact](https://docs.commercetools.com/api/search-query-language.md#exact) expression constraint applies: `value` accepts only a single value. To match against several values within a sort filter, combine multiple `exact` expressions in an [OR expression](https://docs.commercetools.com/api/search-query-language.md#or-expression), rather than passing a list to `value`. All criteria within a sort filter must belong to the same [field level](https://docs.commercetools.com/api/projects/product-search.md#field-levels) as the sort field itself. Since `variants.prices.centAmount` is a price-level field, its sort filter can only reference other price-level fields, such as `channel`, `country`, `currencyCode`, and `customerGroup`. It cannot include product-level fields, such as `sku` or `variants.attributes.*`. This means a price sort cannot be scoped to variants matching a specific Attribute value, such as a `color` Attribute. Because sorting has no knowledge of the `query` or `postFilter` (as noted above), and a price sort's own `filter` cannot reference product-level fields, there is no way to combine both criteria when sorting on a price field. ### Sorting examples #### Sort by CategoryOrderHints [CategoryOrderHints](https://docs.commercetools.com/api/projects/products.md#categoryorderhints) let you define a custom display order for Products within a category. To sort Products by their order hint for a specific category, use the `categoryOrderHints.{categoryId}` field. For best results, combine this sort with a category filter so that only Products belonging to that category are returned: ```json { "query": { "exact": { "field": "categories", "value": "64daee6a-c0e1-42e5-b7a4-a597610b1218" } }, "sort": [ { "field": "categoryOrderHints.64daee6a-c0e1-42e5-b7a4-a597610b1218", "order": "asc" } ], "limit": 20, "offset": 0 } ``` ```graphql { productsSearch( query: { exact: { field: "categories", value: "64daee6a-c0e1-42e5-b7a4-a597610b1218" } } sort: [ { field: "categoryOrderHints.64daee6a-c0e1-42e5-b7a4-a597610b1218", order: asc } ] limit: 20 offset: 0 ) { total results { id } } } ``` #### Sort by Price To sort Products by [Embedded Price](https://docs.commercetools.com/api/pricing-and-discounts-overview.md#embedded-prices), use the `variants.prices.centAmount` field. When sorting in ascending order, the variant with the lowest price determines the Product's sort position. When sorting in descending order, the variant with the highest price is used. Set the `mode` field to `"min"` or `"max"` to make this explicit: ```json { ... "sort": [ { "field": "variants.prices.centAmount", "order": "asc", "mode": "min" } ] } ``` ```graphql { productsSearch( sort: [ { field: "variants.prices.centAmount", order: asc, mode: min } ] ) { total results { id } } } ``` The `variants.prices.centAmount` field covers both [Embedded and Standalone Prices](https://docs.commercetools.com/api/pricing-and-discounts-overview.md#embedded-prices), depending on the `priceMode` of the Product. #### Sort by scoped price Use a `filter` inside the sort object to scope the price sort to a specific currency, country, channel, or customer group: ```json { ... "sort": [ { "field": "variants.prices.centAmount", "filter": { "and": [ { "exact": { "field": "variants.prices.currencyCode", "value": "EUR" } }, { "exact": { "field": "variants.prices.channel", "value": "fb16244b-3963-4b9e-9cb0-69a1f563a854" } } ] }, "order": "asc", "mode": "min" } ] } ``` ```graphql { productsSearch( sort: [ { field: "variants.prices.centAmount" filter: { and: [ { exact: { field: "variants.prices.currencyCode", value: "EUR" } } { exact: { field: "variants.prices.channel", value: "fb16244b-3963-4b9e-9cb0-69a1f563a854" } } ] } order: asc mode: min } ] ) { total results { id } } } ``` #### Sort by values found on variants When a Product has multiple variants, each potentially having a different value for the sort field, the `mode` field controls which variant value is used to rank the Product. The following modes are available: - `min`: uses the lowest value across all variants (default). - `max`: uses the highest value across all variants. - `avg`: uses the average value across all variants. - `sum`: uses the sum of values across all variants. ```json { ... "sort": [ { "field": "variants.attributes.weight", "fieldType": "number", "order": "desc", "mode": "max" } ] } ``` ```graphql { productsSearch( sort: [ { field: "variants.attributes.weight", fieldType: number, order: desc, mode: max } ] ) { total results { id } } } ``` ```json { ... "sort": [ { "field": "variants.attributes.weight", "fieldType": "number", "order": "asc", "mode": "min" } ] } ``` ```graphql { productsSearch( sort: [ { field: "variants.attributes.weight", fieldType: number, order: asc, mode: min } ] ) { total results { id } } } ``` #### Sort by SKU Sort Products alphabetically by variant SKU using the `variants.sku` field: ```json { ... "sort": [ { "field": "variants.sku", "order": "asc" } ] } ``` ```graphql { productsSearch( sort: [ { field: "variants.sku", order: asc } ] ) { total results { id } } } ``` #### Sort by availability Sort Products by variant inventory levels or restocking estimates using the `variants.availability` fields. You can sort by overall availability or by availability within a specific channel: ```json { ... "sort": [ { "field": "variants.availability.availableQuantity", "order": "desc", "mode": "max" } ] } ``` ```graphql { productsSearch( sort: [ { field: "variants.availability.availableQuantity", order: desc, mode: max } ] ) { total results { id } } } ``` ```json { ... "sort": [ { "field": "variants.availability.channels.fb16244b-3963-4b9e-9cb0-69a1f563a854.availableQuantity", "order": "desc", "mode": "max" } ] } ``` ```graphql { productsSearch( sort: [ { field: "variants.availability.channels.fb16244b-3963-4b9e-9cb0-69a1f563a854.availableQuantity", order: desc, mode: max } ] ) { total results { id } } } ``` ```json { ... "sort": [ { "field": "variants.availability.restockableInDays", "order": "asc", "mode": "min" } ] } ``` ```graphql { productsSearch( sort: [ { field: "variants.availability.restockableInDays", order: asc, mode: min } ] ) { total results { id } } } ``` #### Sort by review ratings Sort Products by their aggregated review rating statistics. The following fields are available: - `reviewRatingStatistics.averageRating` - `reviewRatingStatistics.highestRating` - `reviewRatingStatistics.lowestRating` - `reviewRatingStatistics.count` ```json { ... "sort": [ { "field": "reviewRatingStatistics.averageRating", "order": "desc" } ] } ``` ```graphql { productsSearch( sort: [ { field: "reviewRatingStatistics.averageRating", order: desc } ] ) { total results { id } } } ``` #### Sort by Attributes To sort by a Product Variant [Attribute](https://docs.commercetools.com/api/projects/products.md#attribute), the `isSearchable` field of the [AttributeDefinition](https://docs.commercetools.com/api/projects/productTypes.md#attributedefinition) must be set to `true`. Use the `variants.attributes.{name}` field path, with additional sub-fields depending on the Attribute type: (a CSV formatted table follows. The first line are the column names.) Attribute type,Field path,Notes `text`, `boolean`, `number`, `date`, `time`, `datetime`,`variants.attributes.{name}`, `ltext`,`variants.attributes.{name}`,Add `"language": "{lang}"` to the sort object `enum`,`variants.attributes.{name}.key` or `variants.attributes.{name}.label`, `lenum`,`variants.attributes.{name}.key` or `variants.attributes.{name}.label`,For `.label`, add `"language": "{lang}"` to sort by a specific locale `money`,`variants.attributes.{name}.centAmount` or `variants.attributes.{name}.currencyCode`, ```json { ... "sort": [ { "field": "variants.attributes.brand", "fieldType": "text", "order": "asc" } ] } ``` ```graphql { productsSearch( sort: [ { field: "variants.attributes.brand", fieldType: text, order: asc } ] ) { total results { id } } } ``` ```json { ... "sort": [ { "field": "variants.attributes.size.key", "fieldType": "enum", "order": "asc" } ] } ``` ```graphql { productsSearch( sort: [ { field: "variants.attributes.size.key", fieldType: enum, order: asc } ] ) { total results { id } } } ``` ```json { ... "sort": [ { "field": "variants.attributes.color.label", "language": "en", "fieldType": "lenum", "order": "asc" } ] } ``` ```graphql { productsSearch( sort: [ { field: "variants.attributes.color.label", language: "en", fieldType: lenum, order: asc } ] ) { total results { id } } } ``` ### Behavior notes - Missing values: Products that do not have a value for the sort field are placed last in the result, regardless of sort direction. - Indexing delay: Sorting relies on the search index. Changes to Products may take some time to be reflected in sort results due to [indexing delay](https://docs.commercetools.com/api/projects/product-search.md#product-indexing). For more information about sorting Product Search results, see the documentation for the [search query language](https://docs.commercetools.com/api/search-query-language.md#searchsorting). ## Pagination A response to the search request contains the first 20 results by default. Pagination allows you to retrieve the first 10000 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 `20`, you can set the maximum `offset` value to `9 980`. Since you can only retrieve the first 10000 results in total, a higher offset would exceed that limit. In such a case, the API responds with an [InvalidInput](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AInvalidInputError) error with the message "Pagination cannot be used to fetch more than the first 10000 results." ### Pagination examples The following examples show how to retrieve the first and second pages of results. The `offset` value is the number of results to skip, so to request the next page you typically add `limit` to `offset`. Request the first page: ```json title="Request: first page" { "query": { "fullText": { "field": "name", "language": "en", "value": "shoes" } }, "limit": 20, "offset": 0 } ``` ```json title="Response: first page" { "total": 85, "offset": 0, "limit": 20, "count": 20, "results": [...] } ``` Request the second page by advancing `offset` by the value of `limit`: ```json title="Request: second page" { "query": { "fullText": { "field": "name", "language": "en", "value": "shoes" } }, "limit": 20, "offset": 20 } ``` ```json title="Response: second page" { "total": 85, "offset": 20, "limit": 20, "count": 20, "results": [...] } ``` ### Retrieve facets only Set `limit` to `0` to retrieve only facet aggregations without returning any product results. This is useful when you need facet counts for building filter UIs without paying the cost of fetching full Product data: ```json title="Retrieve facet counts without product results" { "query": { "fullText": { "field": "name", "language": "en", "value": "shoes" } }, "facets": [ { "distinct": { "name": "colors", "field": "variants.attributes.color", "fieldType": "text" } } ], "limit": 0, "offset": 0 } ``` ## 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](https://docs.commercetools.com/api/projects/products.md#product) is indexed for Product Search by design. Product Search indexes [Attributes](https://docs.commercetools.com/api/projects/product-search.md#attributes) and [Prices](https://docs.commercetools.com/api/projects/product-search.md#prices) of published Products based on specific limits to ensure optimal performance. ### Attributes Product Search indexes both Product Attributes and Variant Attributes for which the [AttributeDefinition's](https://docs.commercetools.com/api/projects/productTypes.md#attributedefinition) `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 The [indexing strategy](https://docs.commercetools.com/api/projects/product-search.md#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](https://docs.commercetools.com/api/projects/products.md#product) `priceMode` determines which Prices are indexed in the `variants.prices` field. For the `Embedded` [ProductPriceMode](https://docs.commercetools.com/api/projects/products.md#productpricemode), the API indexes [Embedded Prices](https://docs.commercetools.com/api/pricing-and-discounts-overview.md#embedded-prices); for `Standalone`, the API indexes the [Standalone Prices](https://docs.commercetools.com/api/pricing-and-discounts-overview.md#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](https://docs.commercetools.com/api/projects/productProjections.md) 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 10000. 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](https://docs.commercetools.com/api/projects/product-search.md#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 10000 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 10000 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 [commercetools support team](https://support.commercetools.com). 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 15000 Stores per Product. If a Product belongs to more than 15000 Stores, the API will return non-deterministic results for expressions on the `stores` [keyword field](https://docs.commercetools.com/api/projects/product-search.md#keyword-fields). ### Product Selections The search index can hold up to 15000 Product Selections per Product. If a Product belongs to more than 15000 Product Selections, the API will return non-deterministic results for expressions on the `productSelections` [keyword field](https://docs.commercetools.com/api/projects/product-search.md#keyword-fields). ### Localized content To support [exact](https://docs.commercetools.com/api/search-query-language.md#exact), [fullText](https://docs.commercetools.com/api/search-query-language.md#fulltext), [prefix](https://docs.commercetools.com/api/search-query-language.md#prefix), [fuzzy](https://docs.commercetools.com/api/search-query-language.md#fuzzy), and [wildcard](https://docs.commercetools.com/api/search-query-language.md#wildcard) expressions, the Product Search API analyzes the localized product data specific to the [supported languages](https://docs.commercetools.com/api/projects/product-search.md#supported-languages). #### Language settings When Products are indexed, the Product Search API selects the best-fitting language-specific analyzer according to the [Project](https://docs.commercetools.com/urn?urn=ctp%3Aapi%3Atype%3AProject) 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 (a CSV formatted table follows. The first line are the column names.) subtag,language `ar`,Arabic `ca`,Catalan `cs`,Czech `da`,Danish `de`,German `el`,Greek `en`,English `es`,Spanish `eu`,Basque `fa`,Persian `fi`,Finnish `fr`,French `hi`,Hindi `hu`,Hungarian `hy`,Armenian `id`,Indonesian `it`,Italian `ko`,Korean `lv`,Latvian `nl`,Dutch `no`,Norwegian `pl`,Polish `pt`,Portuguese `ro`,Romanian `ru`,Russian `sk`,Slovak `sv`,Swedish `th`,Thai `tr`,Turkish `zh`,Chinese ### Index updates 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](https://docs.commercetools.com/api/projects/product-search.md#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. ## Related pages - [Section overview page](https://docs.commercetools.com/api.md) - [Previous page: Overview](https://docs.commercetools.com/api/storefront-search-overview.md) - [Next page: Product Projection Search](https://docs.commercetools.com/api/projects/product-projection-search.md)