# GraphQL API Access the commercetools commerce API via [GraphQL](https://graphql.org/). The GraphQL API provides queries and mutations to resources using the same [API Clients](/api/projects/api-clients.md) as the HTTP APIs. This page explains how to: - [Apply scopes](/api/graphql.md#scopes) - [Structure GraphQL requests](/api/graphql.md#structure-graphql-requests) - [Check if resources exist](/api/graphql.md#check-if-resources-exist) - [Achieve Reference Expansion](/api/graphql.md#reference-expansion) - [Determine query complexity](/api/graphql.md#query-complexity) - [Perform advanced queries for dynamic Project data and searching](/api/graphql.md#advanced-queries) This page does not document the standard types and fields that the GraphQL API provides. You can retrieve this information either from the GraphQL schema via the [introspection system](https://graphql.org/learn/introspection/) or by using the Documentation Explorer in the [GraphQL Explorer](/merchant-center/developer-settings.md#graphql-explorer). For more information about the types and fields that are currently in [public beta](/offering/compatibility.md#public-beta), see the [Public beta functionalities](/api/graphql.md#public-beta-functionalities) section. The [introspection system](https://graphql.org/learn/introspection/) also lets you discover features that are currently in [early access](/offering/compatibility.md#early-access) and are intended for customers taking part in the early evaluation phase for upcoming features. Be aware that those features are not covered by our SLAs and can either change or be removed without public notice. The GraphQL schema that is publicly available on [GitHub](https://github.com/commercetools/commercetools-api-reference/blob/main/api-specs/graphql/schema.sdl) contains all the officially released API features which are covered by our SLAs. Learn more about how to use the GraphQL API in our self-paced [GraphQL](/learning-developer-essentials/graphql/overview.md) module. The GraphQL API is versionless, meaning new types and fields may be introduced at any time without notice. As a result, dynamic schema stitching is discouraged, as it can lead to naming conflicts and unexpected behavior. ## Interactive GraphQL console To experiment with the GraphQL API, and to view the full types and fields definitions, use the [GraphQL Explorer](/merchant-center/developer-settings.md#graphql-explorer) in the Merchant Center. ## Scopes Access to resources is granted by the same [scopes](/api/scopes.md) that are used on the HTTP API endpoints. This page does not explain about scopes in detail, but you can follow these guidelines to find the most suitable scope for a specific use case: | Use case | GraphQL service | Scope | | --- | --- | --- | | query for a particular resource | `Query.{resourceType}` | `view_{resourceType}s:{projectKey}` | | query for resources of a certain type | `Query.{resourceType}s` | `view_{resourceType}s:{projectKey}` | | create a resource of a certain type | `Mutation.create{resourceType}` | `manage_{resourceType}s:{projectKey}` | | update a particular resource | `Mutation.update{resourceType}` | `manage_{resourceType}s:{projectKey}` | | delete a particular resource | `Mutation.delete{resourceType}` | `manage_{resourceType}s:{projectKey}` | For example, `manage_products:{projectKey}` is required for updating a Product with the `Mutation.updateProduct` service. The `view_products:{projectKey}` scope would not be sufficient for this. The `view_published_products:{projectKey}` scope can be used by the `Query.products` service to retrieve data in `masterData.current`, but not in `masterData.staged`. The same applies for the `Query.productProjectionSearch` service that only returns data with this scope when the `staged` parameter is set to `false` in such queries. Store-specific scopes are required to query or mutate Store-specific resources. For example, the service `Query.inStore` requires `view_{resourceType}s:{projectKey}:{storeKey}`. ## Store-scoped queries and mutations You can scope GraphQL operations to one or more Stores. The list of [Store-scoped API methods](/api/project-configuration-overview.md#store-scoped-api-methods) shows which operations support Store scoping. For an example of retrieving a single Product in a Store context, see [Retrieve Product in Store](/api/graphql.md#retrieve-product-in-store). ### Query for resources in Stores Use the top-level fields `inStore` and `inStores` to query resources belonging only to specified Stores. ```graphql title="Query for Carts in a specific Store with key 'luxury-brand'" query { inStore(key: "luxury-brand") { carts { results { id } total } } } ``` ```graphql title="Query for Carts in multiple Stores" query { inStores(keys: ["luxury-brand", "budget-brand"]) { carts { total } } } ``` Authorization: - You can use regular project-wide scopes (for example `manage_orders:project-key`). - Or use store-based scopes (for example `manage_orders:project-key:luxury-brand`). - For `inStores`, at least one of the provided Store keys must be covered by your scopes; otherwise an `insufficient_scope` error is returned. ### Mutations on resources in Stores Mutations on Carts, Orders, and Customers have an optional `storeKey` argument. When present, the mutation is executed in the context of that Store and fails if the targeted resource does not belong to it. ```graphql title="Create a Cart in the Store with key 'luxury-brand'" mutation { createCart(draft: { currency: "USD" }, storeKey: "luxury-brand") { id } } ``` ```graphql title="Update a Cart only if it is in the Store with key 'luxury-brand'" mutation { updateCart( id: "123e4567-e89b-12d3-a456-426655440000" version: 1 actions: [{ addLineItem: { sku: "..." } }] storeKey: "luxury-brand" ) { id } } ``` Scopes: - Either project-wide (for example `manage_orders:project-key`) or store-based (for example `manage_orders:project-key:luxury-brand`). ## Representations #### GraphQLRequest [type definition](/api/graphql.md?urn=ctp:api:type:GraphQLRequest). #### GraphQLVariablesMap [type definition](/api/graphql.md?urn=ctp:api:type:GraphQLVariablesMap). #### GraphQLResponse [type definition](/api/graphql.md?urn=ctp:api:type:GraphQLResponse). ## Query GraphQL GraphQL uses HTTP POST requests to both query and mutate data. [endpoint definition](/api/graphql.md?urn=ctp:api:endpoint:/\{projectKey}/graphql:POST). ### Using an SDK The following code demonstrates how to query the GraphQL API using the [SDKs](/api/dev-tooling.md). This code assumes you have set up your SDK as described in the get started guide of your respective SDK. You may need to modify some code if your environment differs. ```ts async function graphQLCall() { const q = ` query getProductByKey($productKey: String!) { product(key: $productKey) { id version } } `; return await apiRoot .graphql() .post({ body: { query: q, operationName: 'getProductByKey', variables: { productKey: 'a-product-key', }, }, }) .execute(); } graphQLCall() .then(({ body }) => { console.log(body.data); }) .catch(console.error); ``` ```java GraphQLRequest graphQLQuery = GraphQLRequestBuilder.of() .query("query getProductByKey($productKey: String!) { product(key: $productKey) { id version }}") .operationName("getProductByKey") .variables(GraphQLVariablesMapBuilder.of() .addValue("productKey", "a-product-key") .build()) .build(); GraphQLResponse graphQLCall = apiRoot .graphql() .post(graphQLQuery) .executeBlocking() .getBody(); System.out.println(graphQLCall.getData()); ``` ```cs var graphQLQuery = new GraphQLRequest(){ Query = "query getProductByKey($productKey: String!) { product(key: $productKey) { id version }}", OperationName = "getProductByKey", Variables = new GraphQLVariablesMap() { { "productKey", "a-product-key" } } }; var graphQLCall = await projectApiRoot .Graphql() .Post(graphQLQuery) .ExecuteAsync(); // Output the response Console.WriteLine(graphQLCall.Data.ToString()); ``` The Java SDK's [GraphQL module](/api/dev-tooling/java-sdk-getting-started.md#use-graphql), and the .NET SDK's [GraphQL package](/api/dev-tooling/dotnet-sdk-getting-started.md#use-graphql) provides type-safe GraphQL support. ## Errors If a GraphQL request is unsuccessful, the API returns HTTP status code `400 Bad Request` and the [GraphQLResponse](/api/graphql.md#graphqlresponse) contains an array of [GraphQLError](/api/graphql.md#graphqlerror). #### GraphQLError [type definition](/api/graphql.md?urn=ctp:api:type:GraphQLError). #### GraphQLErrorLocation [type definition](/api/graphql.md?urn=ctp:api:type:GraphQLErrorLocation). #### GraphQLErrorObject [type definition](/api/graphql.md?urn=ctp:api:type:GraphQLErrorObject). ## Structure GraphQL requests The structure of GraphQL calls vary based on whether you want to query, create, update, or delete resources. ### Query existing resources Querying existing resources within your Project uses `query`. A name can be included to describe the call. ```graphql title="Query existing resources" query ReturnCustomers{} ``` #### Add an endpoint In this example, `customers` targets all Customers. To target a single Customer, use `customer`. ```graphql title="Query Customers" query ReturnCustomers{ customers{ } } ``` ```graphql title="Query a single Customer" query ReturnASingleCustomer { customer() { } } ``` #### Add query parameters When querying an endpoint such as `customers` (where all Customers are targeted) these query parameters can be added based on your requirements. | Parameter | Action | Example use | | --- | --- | --- | | where | Only returns resources that match the [Query Predicate](/api/predicates/query.md).Quotation marks within quotation marks must be escaped. | `where: "firstName=\"John\""` | | [sort](/api/general-concepts.md#sorting) | Changes the order of the returned resources. The default sort direction is ascending. | `sort: ["lastName", "title desc"]` | | [limit](/api/general-concepts.md#limit) | The maximum number of results to return. The default value is **20**. | `limit: 5` | | [offset](/api/general-concepts.md#offset) | Used for skipping results. The default value is **0**. | `offset: 1` | Query parameters must be enclosed in `()` next to the endpoint: ```graphql title="Query Customers with parameters" query ReturnCustomers { # Return up to five Customers named "John", ordered by their surname (ascending), and not including the first result customers(where: "firstName=\"John\"", sort: "lastName asc", limit: 5, offset: 1) { } } ``` When querying an endpoint like `customer` (where a single Customer is targeted), you must enter a unique identifier (such as `id` or `key`) for the resource. ```graphql title="Query a single Customer by id" query ReturnASingleCustomer { # Return a Customer based on their id customer(id: "{customerID}") { } } ``` #### Choose what to return When querying an endpoint such as `customers` (where all Customers are targeted) you can return the following: - `offset` is the same value you selected in the parameters. - `count` is the number of resources returned based on the `limit` parameter. - `total` is the total number of resources in your Project. - `exists` returns a boolean value to [indicate whether a queried resource exists](/api/graphql.md#check-if-resources-exist). - `results` is the object where you include the values (such as `id` or `version`) you want to be returned. When querying an endpoint like `customer` (where a single Customer is targeted), you only choose the values of the resource to return. ```graphql title="Complete query for Customers with parameters and values to return" query ReturnCustomers { # Return up to five Customers named "John", ordered by their surname (ascending), and not including the first result customers( where: "firstName=\"John\"" sort: "lastName asc" limit: 5 offset: 1 ) { # Display the offset, count, and total offset count total results { # Return the Customer's ID, version, email, and name. id version email firstName lastName } } } ``` ```graphql title="Complete query for a single Customer by id and values to return" query ReturnASingleCustomer { # Return a Customer based on their id customer(id: "{customerID}") { id version email firstName lastName } } ``` ### Create and update resources When creating or updating resources within your Project, use `mutation`. A name can be included to describe the call. ```graphql title="Create a new resource" mutation CreateDiscountCode{} ``` ```graphql title="Update an existing resource" mutation DeactivateDiscountCode{} ``` #### Add an action Instead of requiring an endpoint, the `mutation` requires an action. The chosen action determines whether you are creating a new resource or updating an existing one. #### When creating a new resource Like with the HTTP API, you must post a draft to create a new resource. You must also include the values you want GraphQL to return once the resource has been created. ```graphql title="Complete query for creating a new resource" mutation CreateDiscountCode { # The action for creating a Discount Code createDiscountCode( # The DiscountCodeDraft and its required fields draft: { code: "SAVE25" isActive: true cartDiscounts: { typeId: "cart-discount", id: "{cartDiscountID}" } } ) { # The values to return code isActive } } ``` #### When updating an existing resource Like with the HTTP API, you must post an array of update actions to modify the data of existing resources. You must also include the values you want GraphQL to return once the resource has been updated. ```graphql title="Complete query for updating an existing resource" mutation DeactivateDiscountCode { # The action to update an existing resource updateDiscountCode( version: 1 id: "{discountCodeID}" ## Include update actions and their required parameters actions: [{ changeIsActive: { isActive: false } }] ) { # The values to return code isActive } } ``` ### Delete resources When deleting resources within your Project, use `mutation`. You can include a name to describe the call. ```graphql title="Delete an existing resource" mutation DeleteDiscountCode { # The action to delete an existing resource deleteDiscountCode(version: 1, id: "{discountCodeID}") { # The values to return id code } } ``` ## Check if resources exist HTTP APIs provide "Check if resource exists" endpoints that use the **HEAD** method and return a `200` or `404` status code to indicate whether a resource exists, based on an identifier or query predicate. In GraphQL, the equivalent functionality is provided by the `exists` field. The GraphQL API either returns `true` if there is at least one result matching the query condition, or `false` if there are none. ```graphql title="Check if a resource exists by id, key, or query predicate" # Quotation marks within quotation marks must be escaped. query checkIfResourceExistsById { customers(where: "id=\"ba5f454f-dab5-4f95-802f-f1c285be7167\"") { exists } } query checkIfResourceExistsByKey { customers(where: "key=\"a-customer-key\"") { exists } } query checkIfResourceExistsByQueryPredicate { customers(where: "firstName=\"John\" and lastName=\"Smith\"") { exists } } ``` ```json title="Example response for checking if a resource exists" { "data": { "customers": { "exists": true } } } ``` ## Reference Expansion The GraphQL API supports [Reference Expansion](/api/general-concepts.md#reference-expansion), just like the HTTP API. By convention, the GraphQL API offers two fields for each expandable reference: - `Ref` - Fetches the [Reference](/api/types.md#reference) or [KeyReference](/api/types.md#keyreference) to the resource only (lower query complexity). - `` - Fetches the expanded resource that is referenced (higher query complexity). Returns `null` if the referenced resource is not found. Expanding a [reference](/api/types.md#references) in the GraphQL API impacts the performance of the request, and adds to the complexity score. If you don't need the expanded reference for your use case, you should use the `Ref` to get a better performance on your query. For example, to retrieve the number of child Categories for a specific Category and the identifiers for its ancestors: ```graphql title="Query for child Categories and ancestors of a Category" { category(id: "7bcd33e6-c1c7-4b96-8d70-9b9b18b19b70") { children { id } ancestors { id } } } ``` ```json title="Example response for children and ancestors" { "data": { "category": { "children": [ { "id": "7900ab4b-c01c-4e73-8f10-557b84de55f5" }, { "id": "93aa3372-310f-4fa7-8ab2-986036382e4f" }, { "id": "0938dbd3-766e-48c2-b86b-31a3a3d820da" } ], "ancestors": [ { "id": "1fbfdc4b-b6c7-4f06-9db4-cbd4179f2a17" } ] } } } ``` The total number of child Categories is the size of the `children` array. The identifiers for ancestors are listed in the `ancestors` field. However, it is possible to reduce the complexity of the previous example. If you only need the number of child Categories, use the `childCount` field. Also, if you only need the identifiers for the ancestors, it is better to use the `ancestorsRef` field. ```graphql title="Query for child count and ancestors reference of a Category" { category(id: "7bcd33e6-c1c7-4b96-8d70-9b9b18b19b70") { childCount ancestorsRef { id } } } ``` ```json title="Example response for child count and ancestors reference" { "data": { "category": { "childCount": 3, "ancestorsRef": [ { "id": "1fbfdc4b-b6c7-4f06-9db4-cbd4179f2a17" } ] } } } ``` ### Custom Fields Reference Expansion for [Custom Fields](/api/projects/custom-fields.md) of [CustomFieldReferenceType](/urn?urn=ctp%3Aapi%3Atype%3ACustomFieldReferenceType) and for the [CustomFieldSetType](/urn?urn=ctp%3Aapi%3Atype%3ACustomFieldSetType) is supported. If, for example, a [Category](/urn?urn=ctp%3Aapi%3Atype%3ACategory) has a Custom Field that holds a [Reference](/api/types.md#reference) to a [Custom Object](/api/projects/custom-objects.md#customobject), the following query expands the referenced Custom Object and retrieves the `value` of the referenced Custom Object in the same query. ```graphql title="Query for a Custom Field that references a Custom Object" query { category(id: "7bcd33e6-c1c7-4b96-8d70-9b9b18b19b70") { custom { customFieldsRaw(includeNames: ["categoryCustom"]) { referencedResource { ... on CustomObject { value } } } } } } ``` ```json title="Expanded Custom Object value in a Custom Field" { "data": { "category": { "custom": { "customFieldsRaw": [ { "referencedResource": { "value": { "extraCategoryData": "This is extra Category data" } } } ] } } } } ``` ### Custom Objects If you have stored References to other resources in values of [Custom Objects](/api/projects/custom-objects.md), you can expand them by using `referencedResources(expand: [String!]): [ReferencedResource!]`. Given an example Custom Object that contains a Reference to a [Review](/urn?urn=ctp%3Aapi%3Atype%3AReview) resource: ```json title="Example Custom Object with Reference to a Review resource" { "id": "687e312c-600a-4544-af21-ca1f6313bfde", "version": 5, ... "container": "myContainer", "key": "myKey", "value": { "reviewRating": { "typeId": "review", "id": "d77f00e0-1cd5-0bfa-b566-bcd105d86edd" } } } ``` The following query represents how to expand the referenced Review contained in the payload of your Custom Object. ```graphql title="Query for expanded text field of a referenced Review resource" query { customObject(container: "myContainer", key: "myKey") { referencedResources(expand: ["reviewRating"]) { path objs { ... on Review { text } } } } } ``` ```json title="Expanded 'text' field on a referenced Review" { "data": { "customObject": { "referencedResources": [ { "path": "reviewRating", "objs": [ { "text": "some review text" } ] } ] } } } ``` If your Custom Object contains a list of References, like the following example containing an array of References to Review resources: ```json title="Example Custom Object with an array of References to Review resources" { "id": "687e312c-600a-4544-af21-ca1f6313bfde", "version": 7, ... "container": "containerForReferences", "key": "listOfReferences", "value": { "reviewReferences": [ { "id": "6e46a6fc-bdee-4159-a07d-ce7b8f6f130f", "typeId": "review" }, { "id": "e8f32564-7963-4c8a-906e-cdc675aba9e8", "typeId": "review" }, { "id": "9902febb-6208-4322-9d7a-d622ec3e4f4f", "typeId": "review" } ] } } ``` you can request for reference expansion of the references contained in the array. For example, you can use the following query to get the `text` field from the expanded Review resources: ```graphql title="Query for expanded 'text' field of all referenced Review resources" { customObject(container: "containerForReferences", key: "listOfReferences") { key referencedResources(expand: "reviewReferences[*]") { path objs { ... on Review { text } } } } } ``` ```json title="Expanded 'text' field on all referenced Reviews" { "data": { "customObject": { "key": "listOfReferences", "referencedResources": [ { "path": "reviewReferences[*]", "objs": [ { "text": "Some review text" }, { "text": "Another review text" }, { "text": "Would buy again" } ] } ] } } } ``` In the previous example we asked for expanding the objects for all references in the array by using the asterisk operator `expand: "reviewReferences[*]"`. The [expansion path](/api/general-concepts.md#expansion-paths) syntax allows you to expand only specific elements of the array also. For Custom Objects, the expansion path cannot contain reference fields that are nested inside the referenced resource. If, for example, you want to expand the `parent` of a referenced [Category](/urn?urn=ctp%3Aapi%3Atype%3ACategory), the expansion path `expand: "category.parent"` **does not work**. Instead, you specify only the top level reference in the expansion path, and query for any nested references in the fragment part of the GraphQL query, like in the following example: ```graphql title="Query for the name of the expanded parent category of a referenced category" query { customObject(container: "custom-object-container", key: "custom-object-key") { referencedResources(expand: ["category"]) { path objs { ... on Category { parent { name(locale: "en") } } } } } } ``` ```json title="Expanded 'name' field on expanded 'parent' field of referenced Category" { "data": { "customObject": { "referencedResources": [ { "path": "category", "objs": [ { "parent": { "name": "some category name" } } ] } ] } } } ``` If your Custom Object value contains references of different resource types, like in the following example: ```json title="Custom Object with references of different resource types" { ... "container": "custom-object-container", "key": "custom-object-key", "value": [ { "category": { "id": "ba5704a1-77b5-4be4-bfb1-24f6a2630d82", "typeId": "category" } }, { "review": { "id": "6e46a6fc-bdee-4159-a07d-ce7b8f6f130f", "typeId": "review" } } ] } ``` you can request expanding both references in the same query by providing both expansion paths: ```graphql title="Query for the name of the expanded parent category of a referenced category" query { customObject(container: "custom-object-container", key: "custom-object-key") { referencedResources(expand: ["category", "review"]) { path objs { ... on Category { parent { name(locale: "en") } } ... on Review { text } } } } } ``` ```json title="Expanded fields on both expansion paths" { "data": { "customObject": { "referencedResources": [ { "path": "category", "objs": [ { "parent": { "name": "root category" } } ] }, { "path": "review", "objs": [ { "text": "awesome test review" } ] } ] } } } ``` ### Variant Attributes You can include `referencedResource` (for [single references](/api/projects/productTypes.md#attributereferencetype)) and `referencedResourceSet` (for [sets of references](/api/projects/productTypes.md#attributesettype)) under `attributesRaw` for `allVariants` or `masterVariant`: ```graphql title="Query for Product Variant Attributes" { product(id: "3ba12359-f03e-4fa1-9c7e-7fbdb5393ec5") { masterData { current { allVariants { attributesRaw { referencedResource { ## If a referenced Product exists, return its name, SKUs, and key ... on Product { masterData { current { slug(locale: "en") } } skus key } ## If a referenced ProductType exists, return the Attributes names and ProductType name ... on ProductTypeDefinition { attributeDefinitions { results { name } } name } } referencedResourceSet { ## If a set of referenced Products exists, return their names, SKUs, and keys ... on Product { masterData { current { slug(locale: "en") } } skus key } ## If a set of referenced ProductTypes exists, return their Attribute names and ProductType names ... on ProductTypeDefinition { attributeDefinitions { results { name } } name } } } } } } } } ``` ### Nested Attributes When using [nested attributes](/urn?urn=ctp%3Aapi%3Atype%3AAttributeNestedType) you can use `attributesRaw` field on `RawProductAttribute` type to gain access to `referencedResource` and `referencedResourceSet`, and therefore expand any references contained within the nested attribute. You can include multiple levels of nested attributes, but doing so increases the total cost of the query. ```graphql { product(id: "3ba12359-f03e-4fa1-9c7e-7fbdb5393ec5") { masterData { current { allVariants { attributesRaw { name attributesRaw { name referencedResource { ## If a referenced Product exists, under a nested reference, return its name, SKUs, and key ... on Product { masterData { current { slug(locale: "en") } } skus key } } } } } } } } } ``` ### Product Projection Search Attributes You can include `referencedResource` (for [single references](/api/projects/productTypes.md#attributereferencetype)) and `referencedResourceSet` (for [sets of references](/api/projects/productTypes.md#attributesettype)) under `attributesRaw` for `masterVariant`. Additionally, when using [nested attributes](/urn?urn=ctp%3Aapi%3Atype%3AAttributeNestedType) you can use the `attributesRaw` field on the `RawProductAttribute` type to gain access to `referencedResource` and `referencedResourceSet`. This expands any references that the nested attribute contains. ```graphql title="Query for Product Projection Search Attributes" { productProjectionSearch(staged: true) { results { masterVariant { attributesRaw( includeNames: "{includedAttributes}" excludeNames: "{excludedAttributes}" ) { name value referencedResource { id ... on Product { skus } } referencedResourceSet { id ... on Product { skus } } } } } } } ``` ## Query complexity You can fetch a lot of information in a single HTTP request using GraphQL. This is helpful to avoid parsing unused fields, and the unnecessary network overhead by reducing the number of requests. At the same time, it is important to remember that a single query can potentially generate a lot of database operations. Hence, you cannot assume that the response time for a query increases linear with the number of fields in the query. The GraphQL schema defines a "cost" per field, which you can display using following options: - Inspecting the `x-graphql-query-complexity` response header and its value. - Using the [GraphQL Explorer](/merchant-center/developer-settings.md#graphql-explorer) query profiler to measure the impact. ![GraphQL Explorer query profiler](https://docs.commercetools.com/api/images/graphql/query-profiler.png) ### QueryComplexityLimitExceeded To prevent complex queries from having a negative impact on the overall performance, we block queries equal to or higher than the complexity limit of 20000. In such cases, queries return a QueryComplexityLimitExceeded error code with the HTTP status code `400`. [type definition](/api/graphql.md?urn=ctp:api:type:QueryComplexityLimitExceededError). ## Advanced queries ### Query Attributes, Custom Objects, and Custom Fields Attributes, Custom Objects, and Custom Fields contain Project-specific dynamic data that can be accessed by using raw GraphQL fields. #### Retrieve Variant Attributes The contents of the `value` field reflect the data type that is used by the Attribute. For more information, see [AttributeType](/api/projects/productTypes.md#attributetype). You must determine the data type yourself. ```graphql title="Example GraphQL request to retrieve Variant Attributes" query { product(id: "3ba12359-f03e-4fa1-9c7e-7fbdb5393ec5") { masterData { current { variants { ...variantFields } } } } } fragment variantFields on ProductVariant { sku attributesRaw { name value } } ``` ```json title="Returned Product Variant Attributes" { "data": { "product": { "masterData": { "current": { "variants": [ { "sku": "M0E20000000E218", "attributesRaw": [ { "name": "productSupported", "value": true }, { "name": "supportContact", "value": { "en": "english@example.com", "de": "german@example.com" } } ] } ] } } } } } ``` #### Retrieve values of enum Attributes in a ProductType The `results` field contains an array of objects containing `name` and `type` of all Attributes in your Project. If `results[*].type.name` is `enum` or `lenum`, an additional `values` field is present, which contains the possible values of the enum Attributes. ```graphql { productType(key: "product-type-key") { attributeDefinitions { results { name type { name ...enumValues ...localizedEnumValues } } } } } fragment enumValues on EnumAttributeDefinitionType { values { results { key label } } } fragment localizedEnumValues on LocalizableEnumAttributeDefinitionType { values { results { key labelAllLocales { locale value } } } } ``` ```json { "data": { "productType": { "attributeDefinitions": { "results": [ { "name": "creationDate", "type": { "name": "datetime" } }, { "name": "articleNumberManufacturer", "type": { "name": "text" } }, { "name": "madeInItaly", "type": { "name": "enum", "values": { "results": [ { "key": "yes", "label": "yes" }, { "key": "no", "label": "no" } ] } } }, { "name": "color", "type": { "name": "lenum", "values": { "results": [ { "key": "black", "labelAllLocales": [ { "locale": "en", "value": "black" }, { "locale": "de", "value": "schwarz" } ] }, { "key": "white", "labelAllLocales": [ { "locale": "de", "value": "weiss" }, { "locale": "en", "value": "white" } ] } ] } } } ] } } } } ``` #### Retrieve Custom Fields The contents of the `value` field reflect the data type that is used by the Custom Field. For more information, see [FieldType](/api/projects/types.md#fieldtype). You must determine the data type yourself. ```graphql title="Example GraphQL request to retrieve Custom Fields" { product(id: "3ba12359-f03e-4fa1-9c7e-7fbdb5393ec5") { masterData { current { variants { prices { ...customFields } } } } } } fragment customFields on ProductPrice { custom { customFieldsRaw { name value } } } ``` ```json title="Returned Product Custom Fields" { "data": { "product": { "masterData": { "current": { "variants": [ { "prices": [ { "custom": { "customFieldsRaw": [ { "name": "kiloPrice", "value": { "type": "centPrecision", "currencyCode": "EUR", "centAmount": 95, "fractionDigits": 2 } } ] } } ] } ] } } } } } ``` ### Create Attributes, Custom Fields, and Custom Objects To set values for Attributes, Custom Objects, and Custom Fields, you must use their corresponding input object types. For each of these, the `value` field should be a string containing escaped JSON. Examples for the `value` field on `ProductAttributeInput`: ```json "{\"type\": \"centPrecision\", \"currencyCode\": \"USD\", \"centAmount\": 1000, \"fractionDigits\": 2}" ``` ```json "\"yellow\"" ``` Examples for the `value` field on `CustomFieldInput`: ```json "[\"This is a string\", \"This is another string\"]" ``` ```json "{\"id\": \"b911b62d-353a-4388-93ee-8d488d9af962\", \"typeId\": \"product\"}" ``` Example for the `value` field on `CustomObjectDraft`: ```json "{ \"stringField\": \"myVal\", \"numberField\": 123, \"boolField\": false, \"nestedObject\": { \"nestedObjectKey\": \"anotherValue\" }, \"dateField\": \"2018-10-12T14:00:00.000Z\" }" ``` ### Differentiate between highPrecision and centPrecision money You can differentiate between [High Precision](/api/types.md#highprecisionmoney) and [Cent Precision](/api/types.md#centprecisionmoney) money in your query. ```graphql query { productsSearch( query: { fullText: { field: "name", value: "blue", language: "en" } } ) { total results { id product { masterData { current { masterVariant { sku prices { value { ...money __typename } discounted { value { ...money __typename } __typename } } } } } } } } } fragment money on BaseMoney { type currencyCode centAmount fractionDigits ... on HighPrecisionMoney { preciseAmount } } ``` ```json { "data": { "productsSearch": { "total": 1, "results": [ { "id": "500797c7-e719-4d72-8c69-92fdee39d451", "product": { "masterData": { "current": { "masterVariant": { "sku": "M0E20000000E582", "prices": [ { "value": { "type": "highPrecision", "currencyCode": "USD", "centAmount": 24875, "fractionDigits": 11, "preciseAmount": 24875123456789, "__typename": "HighPrecisionMoney" }, "discounted": null }, { "value": { "type": "centPrecision", "currencyCode": "EUR", "centAmount": 16311, "fractionDigits": 2, "__typename": "Money" }, "discounted": null } ] } } } } } ] } } } ``` ## Query Products You can query [Product](/urn?urn=ctp%3Aapi%3Atype%3AProduct) data with the `products` query. This query combines the capabilities of the [Products API](/api/projects/products.md) and the [Product Projections API](/api/projects/productProjections.md). In the query selection, you can include fields from the Product representation, including the `current` and `staged` [Product Projections](/api/projects/productProjections.md). You can also project the returned data by locale and select Prices. The `products` query supports the following arguments: - `where`, `sort`, `limit`, and `offset` behave like the [Query Predicates](/api/predicates/query.md) and pagination of the Products API. - `skus`: returns only Products that have a Product Variant with one of the specified SKUs. - `localeProjection`: a list of locales used to filter localized fields. For more information, see [Project by locale](/api/graphql.md#project-by-locale). - `projectExpandedProducts`: a Boolean that defaults to `false`. For more information, see [Project expanded Products](/api/graphql.md#project-expanded-products). The query returns a `ProductQueryResult` with the matching Products in the `results` field. Each entry is a `Product`, so you access the projected data through its `masterData` field. ### Select the current or staged Product Projection A published Product has a `current` [Product Projection](/api/projects/productProjections.md), and every Product has a `staged` Product Projection. Select the `current` field to retrieve published data, or the `staged` field to retrieve data that includes unpublished changes. ```graphql title="Example query to select the current and staged Product Projection" query { products(limit: 20) { total results { key masterData { published hasStagedChanges current { name(locale: "en") } staged { name(locale: "en") } } } } } ``` ### Project by locale Use the `localeProjection` argument to filter localized fields, such as `nameAllLocales`, to the specified locales. All other locales are removed from the response. If a field has no translation for the requested locales, the API falls back to the [Project](/urn?urn=ctp%3Aapi%3Atype%3AProject) languages in the order configured on the Project. ```graphql title="Example query to project Products on a locale" query { products(localeProjection: ["de"]) { results { key masterData { current { nameAllLocales { locale value } } } } } } ``` ### Apply price selection You can apply [price selection](/api/pricing-and-discounts-overview.md#price-selection) on a Product Variant with the `price` field. The arguments have similar names to the Products API, but without the `price` prefix. For example, use `currency` and `country` instead of `priceCurrency` and `priceCountry`. ```graphql title="Example query to apply price selection" query { products { results { key masterData { current { masterVariant { price(currency: "USD", country: "US") { value { centAmount } } } } } } } } ``` ### Retrieve Inventory availability by Channel A Product Variant exposes its Inventory availability through the `availability` field. This field is only populated when the Product Variant has a SKU and at least one [InventoryEntry](/api/projects/inventory.md#inventoryentry) exists for that SKU. Otherwise, `availability` is `null`. ```graphql title="Example query to retrieve Inventory availability by Channel" query { products { results { key masterData { current { masterVariant { availability { noChannel { isOnStock availableQuantity } channels(includeChannelIds: ["channel-id"]) { results { channel { key } availability { isOnStock availableQuantity } } } } } } } } } } ``` You can also restrict the returned Product Variants to those that are in stock for specific supply Channels. Use the `isOnStock` and `stockChannelIds` arguments on the `variants` field. ```graphql title="Example query for Product Variants that are in stock for specific Channels" query { products { results { key masterData { current { variants(isOnStock: true, stockChannelIds: ["channel-id"]) { sku } } } } } } ``` ### Project expanded Products Set `projectExpandedProducts` to `true` to also apply the `localeProjection` to Products that are expanded through Reference-type [Attributes](/api/projects/products.md#attribute). This flag defaults to `false`, which applies the locale projection only to the Products in the `results` field. For more information about expanding references, see [Reference Expansion](/api/graphql.md#reference-expansion). The `products` query doesn't support Store-based projection. To project Products on a [Store](/urn?urn=ctp%3Aapi%3Atype%3AStore), for example to filter Prices or Inventory entries by the Store's Channels, use the `productsSearch` query with the `storeProjection` argument on its `product` field, as described in [Locale and Store projection](/api/graphql.md#locale-and-store-projection). To retrieve a single Product within a Store context, use the `inStore` field with the nested `product` query, as described in [Retrieve Product in Store](/api/graphql.md#retrieve-product-in-store). ### Retrieve Product in Store Use the `inStore` field with the nested `product` query to retrieve a single Product within a Store context. For more information about Store-scoped GraphQL operations, see [Store-scoped queries and mutations](/api/graphql.md#store-scoped-queries-and-mutations). ```graphql title="Example query to retrieve a Product in a Store" query ProductInStore($storeKey: String!, $productId: String!) { inStore(key: $storeKey) { product(id: $productId) { id key masterData { current { name(locale: "en") slug(locale: "en") masterVariant { id } } } } } } ``` ## Use Search queries You can implement storefront search applications using the GraphQL API. ### Use Product Search You can use the functionality of the [Product Search API](/api/projects/product-search.md) with a `productsSearch` query. The `query` and the `postFilter` arguments follow the [SearchQuery](/api/search-query-language.md#searchquery) documentation. The `facets` argument takes [facets](/api/projects/product-search.md#facets) expressions for the Product Search API. Also, the `sort`, `limit` and `offset` arguments behave like documented for the Product Search API, see [sorting](/api/projects/product-search.md#sorting) and [pagination](/api/projects/product-search.md#pagination). ```graphql title="Example query with complex expression, postFilter, sorting, pagination, and facets" query ProductSearchExample { productsSearch( query: { and: [ { exact: { field: "variants.prices.currencyCode", value: "USD" } } { exists: { field: "variants.prices.centAmount" } } { exists: { field: "name", language: "en" } } { fullText: { field: "description" value: "Sample Product" language: "en" } } { range: { long: { field: "variants.prices.centAmount", lte: 15 } } } { range: { datetime: { field: "createdAt", gte: "2024-01-01T00:00:00" } } } { or: [ { exact: { field: "name", value: "Club Mate", language: "en" } } { exact: { field: "name", value: "Spezi", language: "en" } } { exact: { field: "name", value: "Cola", language: "en" } } ] } ] } postFilter: { and: [ { exact: { field: "variants.prices.currencyCode", value: "USD" } } { exists: { field: "variants.prices.centAmount" } } ] } sort: [{ field: "name", order: asc, language: "en" }] limit: 10 offset: 0 markMatchingVariants: true facets: [{ distinct: { name: "productKeys", field: "key", limit: 10 } }] ) { total offset limit results { id product { id } } facets { name } } } ``` The `results` field of the `productsSearch` query contains the Product IDs of the Products matching the query. Additionally, you can obtain the Product data for the matching Products through the `product` sub field, see section [Fetch Product data for the matching Products](/api/graphql.md#fetch-product-data-for-the-matching-products). Moreover, you can provide parameters to the `product` sub field, which allows for [price selection](/api/graphql.md#fetch-product-data-for-the-matching-products) and [locale and Store projection](/api/graphql.md#locale-and-store-projection). #### Full-text search The following request shows a [fullText](/api/search-query-language.md#fulltext) expression for product names in U.S. English. ```graphql query { productsSearch( query: { fullText: { field: "name", value: "bed", language: "en-US" } } ) { total # Uncomment to return the ID of each result. # results { # id # product { # id # } # } } } ``` ```json { "data": { "productsSearch": { "total": 17 } } } ``` #### Search using exact filters The following request shows an [exact](/api/search-query-language.md#exact) expression for prices of 1,599 cents. ```graphql query { productsSearch( query: { exact: { field: "variants.prices.centAmount", value: 1599 } } ) { total results { id product { id version } } } } ``` ```json { "data": { "productsSearch": { "total": 3, "results": [ { "id": "394f20b4-0b21-45f7-81cc-43a4e45ba775", "product": { "id": "394f20b4-0b21-45f7-81cc-43a4e45ba775", "version": 1 } }, { "id": "ecc60e5f-ddd7-4137-8b84-fcaa17426549", "product": { "id": "ecc60e5f-ddd7-4137-8b84-fcaa17426549", "version": 3 } }, { "id": "df65a374-e281-41a1-a370-c4d2591cae39", "product": { "id": "df65a374-e281-41a1-a370-c4d2591cae39", "version": 1 } } ] } } } ``` #### Search using compound expression filters The following request shows a [compound expression](/api/search-query-language.md#compound-expressions) that combines a `fullText` and an `exact` expression. ```graphql query { productsSearch( query: { and: [ { fullText: { field: "name", value: "bed", language: "en-US" } } { exact: { field: "variants.prices.currencyCode", value: "EUR" } } ] } ) { # Return the total number of results. total # Uncomment to return the ID of each result. # results { # id # product { # id # } # } } } ``` ```json { "data": { "productsSearch": { "total": 17 } } } ``` #### Retrieve facet calculation The following example shows a request for calculating [ranges facets](/api/projects/product-search.md#ranges-facets) for prices of the Products. ```graphql query { productsSearch( facets: [ { ranges: { name: "priceRanges" field: "variants.prices.centAmount" ranges: { long: [ { to: 1000 } { from: 1000, to: 5000 } { from: 5000 } ] } } } ] ) { facets { name ... on ProductSearchFacetResultBucket { buckets { key count } } } } } ``` ```json { "data": { "productsSearch": { "facets": [ { "name": "priceRanges", "buckets": [ { "key": "*-1000.0", "count": 45 }, { "key": "1000.0-5000.0", "count": 165 }, { "key": "5000.0-*", "count": 32 } ] } ] } } } ``` #### Fetch all Product Variants The `productsSearch` query provides the following query fields to fetch the Master Variant or the additional Product Variants of a Product Projection separately or all together. - `masterData` - Fetches the selected (current, staged) master data. - `variants` - Fetches only the additional Product Variants. - `allVariants` - Fetches all Product Variants including the Master Variant. ```graphql title="All options for fetching Product Variants" query Products { productsSearch( query: { fullText: { field: "description", value: "pants", language: "en" } } ) { total results { product { key masterData { current { name(locale: "en") variants { sku } allVariants { sku } } } } } } } ``` #### Fetch only matching Product Variants In the `productsSearch` query, you can control whether or not the response contains only the Product Variants that match the search query. To accomplish this, set `markMatchingVariants: true`, and set the `onlyMatching` flag in the `variants` or `allVariants` field to `true`. The flag behaves as follows: - `onlyMatching: true` - Returns only the matching Product Variants. - `onlyMatching: false` - Returns only the non-matching Product Variants. - no flag - Returns all Product Variants. If you use the `onlyMatching` flag without setting `markMatchingVariants` to `true`, the results will be empty even though matching Product Variants may exist. ```graphql title="Example for onlyMatching: true" highlightLines="3,11" query { productsSearch( markMatchingVariants: true postFilter: { exact: { field: "variants.prices.centAmount", value: "1299" } } ) { total results { id product { allVariants(onlyMatching: true) { sku } } } } } ``` #### Fetch Product data for the matching Products 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](/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 } } ``` #### Locale and Store projection ```graphql query { productsSearch( query: { fullText: { field: "name", value: "bed", language: "en-US" } } ) { total results { id product(localesProjection: ["en-US"], storeProjection: "my-store") { masterData { current { name(locale: "en-US") masterVariant { sku price(currency: "USD") { value { currencyCode centAmount } } } } } } } } } ``` ```json { "data": { "productsSearch": { "total": 17, "results": [ { "id": "394f20b4-0b21-45f7-81cc-43a4e45ba775", "product": { "masterData": { "current": { "name": "Classic Wooden Bed", "masterVariant": { "sku": "BED-001", "price": { "value": { "currencyCode": "USD", "centAmount": 89900 } } } } } } } ] } } } ``` ### Use Product Projection Search To be able to use the Product Projection Search endpoint, your product catalog must be indexed first. If indexing is deactivated for your Project, a [SearchDeactivated](/urn?urn=ctp%3Aapi%3Atype%3ASearchDeactivatedError) error is returned. To activate the indexing for your Project, choose one of the following options: - via API using the [Change Product Search Indexing Enabled](/api/projects/project.md#change-product-search-indexing-enabled) update action on the Project endpoint. - via the [Merchant Center](/docs/login.md) by navigating to **Settings** > **Project settings** > **Storefront Search**. - via contacting the [commercetools support team](https://support.commercetools.com/) and provide your region, Project key, and use case. #### Search based on text and locale ```graphql query { productProjectionSearch(staged: true, locale: "en-US", text: "bed") { total # Uncomment to return the ID and English name of each result. # results{ # id # name(locale:"en-US") # } } } ``` ```json { "data": { "productProjectionSearch": { "total": 17 } } } ``` #### Search based on text, locale, and filtering ```graphql query { productProjectionSearch( queryFilters: [{ string: "published:true" }] staged: true locale: "en-US" text: "bed" ) { # Return the total number of results. total # Uncomment to return the ID and English name of each result. # results{ # id # name(locale:"en-US") # } } } ``` ```json { "data": { "productProjectionSearch": { "total": 17 } } } ``` #### Search using filters ```graphql query { productProjectionSearch( staged: true filters: [ { model: { value: { path: "variants.price.centAmount", values: ["1599"] } } } ] ) { total results { id version } } } ``` ```json { "data": { "productProjectionSearch": { "total": 3, "results": [ { "id": "394f20b4-0b21-45f7-81cc-43a4e45ba775", "version": 1 }, { "id": "ecc60e5f-ddd7-4137-8b84-fcaa17426549", "version": 3 }, { "id": "df65a374-e281-41a1-a370-c4d2591cae39", "version": 1 } ] } } } ``` #### Retrieve facet calculation ```graphql query { productProjectionSearch( facets: [ { model: { range: { path: "variants.price.centAmount" ranges: [{ from: "1000", to: "3000" }] countProducts: false } } } ] ) { facets { facet value { type ... on RangeFacetResult { dataType ranges { type ... on RangeCountDouble { from fromStr to toStr count productCount totalCount total min max mean } } } } } } } ``` ```json { "data": { "productProjectionSearch": { "facets": [ { "facet": "variants.price.centAmount", "value": { "type": "range", "dataType": "number", "ranges": [ { "type": "double", "from": 1000, "fromStr": "1000.0", "to": 3000, "toStr": "3000.0", "count": 35, "productCount": null, "totalCount": 35, "total": 62065, "min": 1099, "max": 2999, "mean": 1773.2857142857142 } ] } } ] } } } ``` #### Differentiate between highPrecision and centPrecision money ```graphql { productProjectionSearch(staged: false, locale: "en", text: "blue") { count results { id masterVariant { sku prices { ...productPrice } } } } } fragment productPrice on ProductPriceSearch { value { ...money __typename } discounted { value { ...money __typename } __typename } } fragment money on BaseMoney { type currencyCode centAmount fractionDigits ... on HighPrecisionMoney { preciseAmount } } ``` ```json { "data": { "productProjectionSearch": { "results": [ { "id": "500797c7-e719-4d72-8c69-92fdee39d451", "masterVariant": { "sku": "M0E20000000E582", "prices": [ { "value": { "type": "highPrecision", "currencyCode": "USD", "centAmount": 24875, "fractionDigits": 11, "preciseAmount": 24875123456789, "__typename": "HighPrecisionMoney" }, "discounted": null }, { "value": { "type": "centPrecision", "currencyCode": "EUR", "centAmount": 16311, "fractionDigits": 2, "__typename": "Money" }, "discounted": null } ] } } ] } } } ``` #### Fetch all Product Variants The `productProjectionSearch` query provides the following query fields to fetch the Master Variant or the additional Product Variants of a Product Projection separately or all together. - `masterVariant` - Fetches the Master Variant only. - `variants` - Fetches only the Product Variants in addition to the Master Variant. - `allVariants` - Fetches all Product Variants including the Master Variant. ```graphql title="All options for fetching Product Variants" query { productProjectionSearch { results { masterVariant { key } variants { id } allVariants { sku } } } } ``` #### Fetch only matching Product Variants In the `productProjectionSearch` query, you can control whether or not the response contains only the Product Variants that match the search query. To accomplish this, set `markMatchingVariants: true`, and set the `onlyMatching` flag in the `variants` or `allVariants` field to `true`. The flag behaves as follows: - `onlyMatching: true` - Returns only the matching Product Variants. - `onlyMatching: false` - Returns only the non-matching Product Variants. - no flag - Returns all Product Variants. If you use the `onlyMatching` flag without setting `markMatchingVariants` to `true`, the results will be empty even though matching Product Variants may exist. ```graphql title="Example for onlyMatching: true" highlightLines="3,10" query { productProjectionSearch( markMatchingVariants: true queryFilters: { model: { value: { path: "variants.price.centAmount", values: ["1299"] } } } ) { results { key allVariants(onlyMatching: true) { sku } } count } } ``` ### Use Search Term Suggestions You can use the functionality of the [Search Term Suggestions API](/api/projects/search-term-suggestions.md) with a `productProjectionsSuggest` query. ```graphql title="Example query for suggested search terms for English input 'swiss'" query { productProjectionsSuggest( searchKeywords: { searchKeyword: "swiss", locale: "en" } ) { searchKeywords { suggestions { text } } } } ``` ```json title="Example response for predefined search keywords for English: 'swiss'" { "data": { "productProjectionsSuggest": { "searchKeywords": [ { "suggestions": [ { "text": "Swiss Army Knife" } ] } ] } } } ``` ## Public beta functionalities Find below a list of functionality that is currently in [beta](/offering/compatibility.md#public-beta). - `createdBy` and `lastModifiedBy` on `Versioned` - `MultiBuyLineItemsTarget` and `MultiBuyCustomLineItemsTarget` on `CartDiscount` - `stores` on `CartDiscount` and `DiscountCode` - `NestedTypes` on `ProductType` - `attributesRaw` on `RawProductAttribute`, - everything related to `MyCart`, `MyOrder`, `MyPayment`, `MyProfile`, `MyShoppingList`, `MyBusinessUnit`, `MyQuote`, `MyQuoteRequest` - everything related to `ProductCatalogModel` and `Variants` ## Related pages - [Area overview page with navigation](/api.md) - [Previous page: Limits](/api/limits.md)