# Storefront search APIs overview

**How to work with the APIs for storefront search.**

This page describes rules that apply across the Storefront Search APIs. For what Product Search is and how it behaves, see the [Storefront search overview](/api/storefront-search-overview.md).

## APIs in this topic

| Resource | Purpose | Reference |
| --- | --- | --- |
| Product Search | Create storefront discovery experiences where customers can browse and search across your Products. | [Product Search](/api/projects/product-search.md) |

## Working with the APIs

### Product Search

In `Classic` Projects, Product Search works primarily with Product Projections rather than with the entire, complex Product resource. In `Modular` Projects, Variant-level data additionally requires a separate [Variant Projections](/api/projects/variant-projections.md) query.

Product Search supports Projects with the [ProductCatalogModel](/api/projects/project.md#productcatalogmodel) set to `Classic`. Projects with the [ProductCatalogModel](/api/projects/project.md#productcatalogmodel) set to `Modular` and `InMigration` are supported in .

The Product Search API accepts queries expressed in the [search query language](/api/search-query-language.md).

The API supports only those [Locales](/api/types.md#locale) configured in the [Project](/search.md?urn=ctp:api:type:Project).
Queries for Locales not configured in the Project do not return any result.

Product Search does not support sorting, filtering, or faceting with [tailored product data](/api/projects/product-tailoring.md).

Only the [current representation](/api/product-catalog-overview.md#current) of [Products](/api/projects/products.md#product) is indexed for Product Search.

When you access Product Search with an [external OAuth token](/api/authorization.md#request-an-access-token-using-an-external-oauth-server), explicitly include `view_products:{projectKey}` in the token scope. Don't rely on implied permissions when defining the token scope.

### 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](/docs/login.md) by navigating to **Settings** > **Project settings** > **Storefront Search**.
- using the [Change Product Search Indexing Enabled](/api/projects/project.md#change-product-search-indexing-enabled) update action with `mode: ProductsSearch` on the [Update Project](/search.md?urn=ctp:api:endpoint:/{projectKey}:POST) endpoint.

Once activated for your Project, your [Products get indexed](/api/storefront-search-apis-overview.md#product-indexing), and the [Search Products](/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](/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](/search.md?urn=ctp:api:type:ObjectNotFoundError) error.

You can reactivate the Product Search API as described above.

### 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 the additional methods described in [Data integration](/api/projects/product-search.md#data-integration).

### Compare product retrieval methods

You can use the following 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](/api/storefront-search-apis-overview.md#product-indexing): the product information might not always reflect the latest state.

**[Query Product Projections](/search.md?urn=ctp:api:endpoint:/{projectKey}/product-projections:GET)** 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](/search.md?urn=ctp:api:type:Product) resource.

> Do not use this method to retrieve current product stock levels.
> The `availability` of a ProductVariant in a Product Projection is [eventually consistent](/api/general-concepts.md#eventual-consistency) and may not reflect immediate stock.
> Instead, use the [Inventory API](/api/projects/inventory.md).

**[Query VariantProjections](/search.md?urn=ctp:api:endpoint:/{projectKey}/variant-projections:GET)** returns lightweight, read-only representations of individual Product Variants with embedded Product data.
Use this method for storefront pages that need Variant-level data under the `Modular` [ProductCatalogModel](/api/projects/project.md#productcatalogmodel). For more information, see [Variant Projections](/api/projects/variant-projections.md).

**[Query Products](/search.md?urn=ctp:api:endpoint:/{projectKey}/products:GET)** returns entire [Products](/api/projects/products.md#product) with both `current` and `staged` representations.
Use this method only for product data management in backoffice applications.

### Product indexing

Indexing is a process carried out by the API to build the search index for your product information, which is optimized for read access with low response times.
If you retrieve product information using the [Products API](/api/projects/products.md) or [Product Projections API](/api/projects/productProjections.md) before indexing is complete, the product data in the search index might be different.
The search index is automatically updated whenever Products are changed.

| Functionality | Product Search |
| --- | :---: |
| Indexed projection of the Products | current |
| Support for Variant-level [Attributes](/search.md?urn=ctp:api:type:Attribute) | `variants.attributes.<attribute-name>` [expression](/api/projects/product-search.md#attributes), up to **50** Attributes per Product Variant |
| Support for Product-level [Attributes](/search.md?urn=ctp:api:type:Attribute) | `attributes.<attribute-name>` [expression](/api/projects/product-search.md#attributes), up to **50** Attributes per Product Variant |
| Support for [Standalone Prices](/api/projects/standalone-prices.md) | for [sorting, filtering, and faceting](/api/storefront-search-apis-overview.md#prices), up to **10000** Standalone Prices per Product |
| Support for validity periods of [Prices](/api/types.md#price) | `variants.prices.*` [keyword fields](/api/projects/product-search.md#keyword-fields) |
| Support for [Stores](/api/projects/stores.md) | `stores` [keyword field](/api/projects/product-search.md#keyword-fields), up to **15000** Stores per Product |
| Support for [Product Selections](/api/projects/product-selections.md) | `productSelections` [keyword field](/api/projects/product-search.md#keyword-fields), up to **15000** Product Selections per Product |
| Support for [Product Tailoring](/api/projects/product-tailoring.md) | with [GraphQL](/api/graphql.md#use-product-search) |

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](/api/projects/products.md#product) is indexed for Product Search by design.
Product Search indexes [Attributes](/api/storefront-search-apis-overview.md#attributes) and [Prices](/api/storefront-search-apis-overview.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](/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&#x20;

The [indexing strategy](/api/storefront-search-apis-overview.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](/api/projects/products.md#product) `priceMode` determines which Prices are indexed in the `variants.prices` field.
For the `Embedded` [ProductPriceMode](/api/projects/products.md#productpricemode), the API indexes [Embedded Prices](/api/pricing-and-discounts-overview.md#embedded-prices); for `Standalone`, the API indexes the [Standalone Prices](/api/pricing-and-discounts-overview.md#standalone-prices) associated with the Product Variant.
The `Modular` [ProductCatalogModel](/api/projects/project.md#productcatalogmodel) supports only Standalone Prices.

The API uses the `variants.prices` field for sorting, filtering, and faceting on Products, but its content is not retrievable through the Product Search API.
Standalone Prices **cannot be retrieved** through the Product Search API.
To retrieve Price data, use the GraphQL API or the [Product Projection API](/api/projects/productProjections.md) for Embedded Prices. In Projects with the [ProductCatalogModel](/api/projects/project.md#productcatalogmodel) set to `Modular`, use the [Variant Projections API](/api/projects/variant-projections.md#prices).

To ensure good performance of the API, the number of Standalone Prices indexed per Product is limited to 10000 in `Classic` Projects.
In Projects with the [ProductCatalogModel](/api/projects/project.md#productcatalogmodel) set to `Modular`, indexed [Variants](/api/projects/variants.md#variant) and indexed Standalone Prices of a Product share this same 10000 limit. The indexer prioritizes Variants, indexing as many Variants of the Product as possible first, then uses any remaining capacity within the limit to index Standalone Prices.
For Products with Standalone Prices that exceed the available limit, the system indexes a selection of the Prices as explained in the [Large number of Standalone Prices](/api/storefront-search-apis-overview.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

In `Classic` Projects, the search index can hold up to **10000** Standalone Prices per Product.
In `Modular` Projects, this same limit is shared with indexed Variants, as described in [Prices](/api/storefront-search-apis-overview.md#prices); The indexer fills this shared limit with Variants first, then indexes Standalone Prices using whatever capacity remains.
For example, a Product has 9500 Variants and 1200 Standalone Prices. Since Variants are indexed first, they consume 9500 of the 10,000 limit, leaving only 500 slots for Standalone Prices. As a result, only 500 of the 1200 Standalone Prices can be indexed.

If a Product has more Standalone Prices than the remaining capacity allows, the API applies a sorting algorithm when indexing Product Prices to ensure a fair distribution of indexed Prices across all Product Variants.

For each Product in the Project:

1. Calculate the per-Variant Price limit. To do this, divide the available Standalone Prices capacity (**10000** in `Classic` Projects, or the remaining capacity after Variants are indexed in `Modular` Projects) by the number of Variants on the Product. This gives the maximum number of Standalone Prices indexed per Variant.
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.

###### Examples

- **Example 1:** A Product has 4 Variants and 1000 Standalone Prices. Each Variant is allowed a maximum of 250 (1000 / 4) indexed Standalone Prices. If a Variant has 300 Standalone Prices, they are sorted by currency, then country, then channel, then Customer Group. Only the first 250 (in that sorted order) are indexed; the remaining 50 are discarded.

- **Example 2:** A Product has 10,500 Variants, which exceeds the available Standalone Prices capacity of 10,000. Since Variants are indexed first, no capacity remains for Standalone Prices, so the per-Variant Price limit is 0. As a result, none of the Product's Standalone Prices are indexed.

##### 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](/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](/api/projects/product-search.md#keyword-fields).

#### Localized content

To support [exact](/api/search-query-language.md#exact), [fullText](/api/search-query-language.md#fulltext), [prefix](/api/search-query-language.md#prefix), [fuzzy](/api/search-query-language.md#fuzzy), and [wildcard](/api/search-query-language.md#wildcard) expressions, the Product Search API analyzes the localized product data specific to the [supported languages](/api/storefront-search-apis-overview.md#supported-languages).

##### Language settings

When Products are indexed, the Product Search API selects the best-fitting language-specific analyzer according to the [Project](/search.md?urn=ctp:api:type:Project) settings. If no `languages` are set on the Project, the analyzers for the following languages are used by default: `en`, `en-GB`, `en-CA`, `en-US`, `en-AU`, `de`, `de-DE`, `es`, `se`, `fi`, `da`, `fr`, and `fr-FR`.

> Any language that is specified on the Product field, but not defined in the Project settings, will make that localized Product field **not searchable**.
>
> **Example**: A Project has language settings defined for `en` and `de`, and a Product has localized `name` fields, one for `name.ro` and one for `name.de`.
> Since the Romanian language is not part of the Project language settings, the `name.ro` field is not searchable, only the `name.de` field is in this case.

##### Supported languages

| 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](/api/storefront-search-apis-overview.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.

### Query expressions

| Functionality | Product Search |
| --- | :---: |
| Full-text search | [fullText](/api/search-query-language.md#fulltext) expression |
| Fuzzy search | [fuzzy](/api/search-query-language.md#fuzzy) expression |
| Exact match | [exact](/api/search-query-language.md#exact) expression |
| Range of values | [range](/api/search-query-language.md#range) expression |
| If field exists | [exists](/api/search-query-language.md#exists) expression |
| Prefix match | [prefix](/api/search-query-language.md#prefix) expression |
| Wildcard match | [wildcard](/api/search-query-language.md#wildcard) expression |
| Boosting particular searchable fields | [boosting](/api/search-query-language.md#boost-query-results) |
| Specify query expressions per searchable field | [by field name](/api/search-query-language.md#searchable-fields) |
| Operators to define compound expressions | `and`, `or`, `not`, `filter` [operators](/api/search-query-language.md#compound-expressions) |

### Query result

By default, the Product Search API returns only the IDs of the matching Products. You can use the returned Product IDs to retrieve the full projection of a Product ([Get Product Projection by ID](/search.md?urn=ctp:api:endpoint:/{projectKey}/product-projections/{id}:GET)).

Where the Variant data comes from depends on the [ProductCatalogModel](/api/projects/project.md#productcatalogmodel) of the Project, `Classic` or `Modular`. For details, see [Data integration](/api/projects/product-search.md#data-integration).

| Functionality | Product Search |
| --- | :---: |
| IDs of the matching Products | by default |
| Full representations of the matching Products | with [GraphQL](/api/graphql.md#use-product-search) |
| [Price selection](/api/pricing-and-discounts-overview.md#price-selection) | with [GraphQL](/api/graphql.md#use-product-search) |
| [Scoped Price search](/api/projects/product-projection-search.md#scoped-price-search) | with [filter by price](/api/projects/product-search.md#filter-by-price) |

### Full-text search

| Functionality | Product Search |
| --- | :---: |
| Specific to different languages | [supported languages](/api/storefront-search-apis-overview.md#supported-languages) |
| Allow to specify which Product fields should be taken into account. | `language` parameter in [fullText](/api/search-query-language.md#fulltext) expression |

### Fuzzy search

| Functionality | Product Search |
| --- | :---: |
| The [fuzziness level](/api/search-query-language.md#fuzziness-level) adjusts automatically based on term length. | Set maximum level with `level` parameter in [fuzzy](/api/search-query-language.md#fuzzy) expression. |
| Allows you to specify which Product fields to include. | `field` parameter in [fuzzy](/api/search-query-language.md#fuzzy) expression |

### Filtering

| Functionality | Product Search |
| --- | :---: |
| Non-facet-driven filter (for example by Category, Attribute values, full-text search) that both results and every facet's base set inherit. | `query` in [ProductSearchRequest](/search.md?urn=ctp:api:type:ProductSearchRequest) |
| Narrow the set of returned products after facets are calculated. | `postFilter` in [ProductSearchRequest](/search.md?urn=ctp:api:type:ProductSearchRequest) |
| Narrow the calculated facet results. | `filter` in facet expressions |

### Faceting

| Functionality | Product Search |
| --- | :---: |
| Count all occurrences of distinct values. | [distinct facets](/api/projects/product-search.md#distinct-facets) |
| Count Products that have values within a specified range. | [ranges facets](/api/projects/product-search.md#ranges-facets) |
| Count the number of Products or Product Variants. | [count facets](/api/projects/product-search.md#count-facets) |
| Calculate statistics for facet values. | [stats facets](/api/projects/product-search.md#stats-facets) |
| Allow for handling missing values when calculating facets. | [missing values](/api/projects/product-search.md#missing-values) |
| Filter facet results. | [filter facet](/api/projects/product-search.md#filter-facet) |
| Specify scope of facets. | [scope of facets](/api/projects/product-search.md#scope-of-facets) |
| Assign a custom name to a facet result. | `name` in facet expression |

### Sorting

| Functionality | Product Search |
| --- | :---: |
| Sort order (ascending or descending) | `asc` and `desc` [sort order](/api/search-query-language.md#sort-order) |
| Sort mode to specify which value to sort on when a Product contains multiple values for the field | `min`, `max`, `avg`, `sum` [sort mode](/api/search-query-language.md#sort-mode) |
| Specify exactly which resources should be considered for sorting. | [sort filter](/api/search-query-language.md#sort-filter) |
| Sort by a localized field, such as `name` | `language` property on the [sort object](/api/projects/product-search.md#sort-by-standard-fields) |
| Sort by [CategoryOrderHints](/api/projects/products.md#categoryorderhints) | [structured sort object](/api/projects/product-search.md#sort-by-categoryorderhints) |

### Pagination

| Functionality | Product Search |
| --- | :---: |
| Maximum results per page (`limit`) | 100 |
| Maximum total results reachable through pagination | first **10000** results |
| Error when the offset exceeds the maximum | [InvalidInput](/search.md?urn=ctp:api:type:InvalidInputError) error |

## Limits

Product Search supports searching through Products across the following resources:

| Resource | Limit per Product |
| --- | --- |
| [Stores](/api/projects/stores.md#store) | **15000** |
| [Product Selections](/api/projects/product-selections.md#productselection) | **15000** |
| [Standalone Prices](/api/projects/standalone-prices.md#standaloneprice) | **10000** |

These limits are soft:

- If you exceed the limits for Stores or Product Selections, the API returns non-deterministic results.
- If you exceed the limit for Standalone Prices, the API applies a [sorting algorithm](/api/storefront-search-apis-overview.md#large-number-of-standalone-prices) during Product indexing to offer a balanced selection of Prices across Variants.

For performance reasons, the API imposes some [limitations](/api/limits.md#product-search).
Follow our [performance tips](/api/performance-tips.md#product-search) to minimize the response times for the Product Search API.

## Related pages

- [Area overview page with navigation](/api.md)
- [Previous page: Overview](/api/storefront-search-overview.md)
- [Next page: Product Search](/api/projects/product-search.md)
- [Search documentation and API specs](/search.md)