Learn how to use Modular Catalog to model Products with large numbers of Variants, display them on a storefront, and create an Order from them.
Product Variants can be modeled in two ways:
- The
Classicmodel: where Product Variants are embedded within the Product. This model is suitable for use cases with up to 100 Variants per Product. - The
Modularmodel: where Product Variants are standalone Variant resources linked to the Product. Using the modular model removes the 100-variant limit of the Classic model and provides more flexibility for brands with large and complex catalogs.
In this tutorial, you will follow the journey of a system integrator building a storefront for an outdoor clothing brand. The brand sells a jacket that comes in multiple colors and sizes, which has far more combinations than allowed when the Product Variant is embedded within the Product. You will learn to:
- Enable the
Modularmodel on a Project. - Create a Product and add Variants to it.
- Publish individual Variants independently.
- Retrieve Variant data for a PDP, including Variant selectors.
- Add a Variant to a Cart and create an Order.
Prerequisites
Before you begin, you should be familiar with:
- commercetools concepts such as Products, Product Types, Carts, and Orders.
- Using a REST API client such as Postman to interact with the commercetools APIs.
- General concepts around Project setup and API Client management in the Merchant Center.
- Standalone Prices, as in modular mode, all prices are managed as Standalone Prices linked to a Variant SKU, not embedded in the product.
You should also have a commercetools Project with the following:
- A Product Type: a Product Type that defines the Variant attributes you want to use. In this tutorial, the Product Type includes color (lenum) and size (enum) attributes.
- Standalone Prices: at least one Standalone Price for the Variant SKU you will create.
- API Client: an API Client with the following scopes:
manage_products:{projectKey}— to create and update Products and Variants.manage_project_settings:{projectKey}— to enable Modular mode.manage_orders:{projectKey}— to create and update Carts and Orders.view_published_products:{projectKey}— to query Variant Projections from the storefront.
Enable Modular mode
Before you can use the Variant APIs, you must switch your Project to Modular mode. This is a one-time Project-level setting that changes how Variants are managed.
Modular and Classic mode. This tutorial assumes a new Project or environment with no existing embedded Variants.curl -X POST "https://api.europe-west1.gcp.commercetools.com/{project-key}" \
-H "Authorization: Bearer {auth-token}" \
-H "Content-Type: application/json" \
-d '{
"version": 1,
"actions": [
{
"action": "setProductCatalogModel",
"productCatalogModel": "Modular"
}
]
}'
productCatalogModel is set to Modular.Create a Product
In modular mode, a Product is a container for shared data such as name, description, and Product-level attributes. Variants are created separately and linked to it.
masterVariant, variants) when creating a Product. Create the Product first, then add Variants using the Variants endpoint.curl -X POST "https://api.europe-west1.gcp.commercetools.com/{project-key}/products" \
-H "Authorization: Bearer {auth-token}" \
-H "Content-Type: application/json" \
-d '{
"productType": { "typeId": "product-type", "id": "{product-type-id}" },
"name": { "en": "Outdoor Jacket" },
"slug": { "en": "outdoor-jacket" },
"publish": true
}'
Take note of the ID of the created Product; you will need it in the next step. The Product is published immediately because a Variant can only be published if its parent Product is already published.
Add Variants
Now you can create Variants and associate them with the Product. Each Variant represents a unique SKU, in this case, a specific combination of color and size.
Create a Variant
Create the first Variant of a red jacket in size M:
curl -X POST "https://api.europe-west1.gcp.commercetools.com/{project-key}/variants" \
-H "Authorization: Bearer {auth-token}" \
-H "Content-Type: application/json" \
-d '{
"product": { "typeId": "product", "id": "{product-id}" },
"key": "outdoor-jacket-red-m",
"sku": "JACKET-RED-M",
"attributes": [
{ "name": "color", "value": "red" },
{ "name": "size", "value": "m" }
],
"images": [
{ "url": "https://example.com/jacket-red.jpg", "dimensions": { "w": 800, "h": 800 } }
],
"publish": false
}'
"published": false). The staged field is null because no staged changes exist yet and the Variant data is available in "current".{
"id": "variant-uuid",
"variantId": 1,
"key": "outdoor-jacket-red-m",
"product": { "typeId": "product", "id": "{product-id}" },
"current": {
"sku": "JACKET-RED-M",
"attributes": [
{ "name": "color", "value": { "key": "red", "label": { "en": "Red" } } },
{ "name": "size", "value": { "key": "m", "label": "M" } }
],
"images": [{ "url": "https://example.com/jacket-red.jpg" }],
"assets": []
},
"staged": null,
"published": false
}
variantId that is unique within the Product.Update the Variant's staged data
setAttributes update action with "staged": true:curl -X POST "https://api.europe-west1.gcp.commercetools.com/{project-key}/variants/{variant-id}" \
-H "Authorization: Bearer {auth-token}" \
-H "Content-Type: application/json" \
-d '{
"version": 1,
"actions": [
{
"action": "setAttributes",
"attributes": [
{ "name": "color", "value": "red" },
{ "name": "size", "value": "m" }
],
"staged": true
}
]
}'
Publish a Variant
Publishing a Variant makes it visible in the storefront via the Variant Projection API. Variants can be published independently, you do not need to republish the entire Product.
curl -X POST "https://api.europe-west1.gcp.commercetools.com/{project-key}/variants/{variant-id}" \
-H "Authorization: Bearer {auth-token}" \
-H "Content-Type: application/json" \
-d '{
"version": 2,
"actions": [
{ "action": "publish" }
]
}'
"published": true confirms the Variant is now live. Only this specific Variant is affected, other Variants on the same Product remain in their current state.Variant lifecycle rules
- A Variant can only be published if its parent Product is published.
- A Variant can be unpublished independently at any time.
- A Product cannot be unpublished while it still has published Variants, you must unpublish all Variants first.
- A Product cannot be deleted while any Variants reference it, you must delete all Variants first.
Display Variants on a PDP
The storefront uses two dedicated read endpoints to render a PDP efficiently, even for products with thousands of Variants.
Fetch the selected Variant
When a customer lands on a PDP and selects a specific Variant, use the Variant Projection endpoint to retrieve the full data for that Variant including the denormalized product name, slug, attributes, images, and a resolved price.
curl -X GET "https://api.europe-west1.gcp.commercetools.com/{project-key}/variant-projections?where=sku%3D%22JACKET-RED-M%22&priceCurrency=EUR&priceCountry=DE" \
-H "Authorization: Bearer {auth-token}"
200 response merges Product-level and Variant-level data into a single object, ready for the PDP:{
"results": [
{
"id": "variant-uuid",
"variantId": 1,
"sku": "JACKET-RED-M",
"name": { "en": "Outdoor Jacket" },
"slug": { "en": "outdoor-jacket" },
"attributes": [
{ "name": "color", "value": { "key": "red", "label": { "en": "Red" } } },
{ "name": "size", "value": { "key": "m", "label": "M" } }
],
"images": [{ "url": "https://example.com/jacket-red.jpg" }],
"price": { "value": { "centAmount": 18900, "currencyCode": "EUR" } }
}
]
}
priceCurrency is required to get a resolved price in the response. If omitted, the price field is not included.Fetch all Variants
To render the color and size selectors on the PDP, and correctly indicate which combinations are available, use the Variant Attributes endpoint. This endpoint returns lightweight data for all Variants of a Product at once, designed to handle up to 10,000 Variants efficiently.
curl -X GET "https://api.europe-west1.gcp.commercetools.com/{project-key}/product-projections/{product-id}/variant-attributes?filter%5Battributes%5D=color&filter%5Battributes%5D=size" \
-H "Authorization: Bearer {auth-token}"
filter[attributes] is required and you must specify which attributes to include. Omitting it returns a 400 Bad Request.200 response returns a slim payload for every published Variant, with availability included:{
"productId": "{product-id}",
"productKey": "outdoor-jacket",
"attributes": [
{ "name": "color", "label": { "en": "Color" }, "type": "lenum" },
{ "name": "size", "label": { "en": "Size" }, "type": "enum" }
],
"variants": [
{
"id": "variant-uuid-1",
"sku": "JACKET-RED-M",
"availability": { "noChannel": { "isOnStock": true, "availableQuantity": 15 } },
"attributes": {
"color": { "key": "red", "label": { "en": "Red" } },
"size": { "key": "m", "label": "M" }
}
},
{
"id": "variant-uuid-2",
"sku": "JACKET-BLUE-L",
"availability": { "noChannel": { "isOnStock": false, "availableQuantity": 0 } },
"attributes": {
"color": { "key": "blue", "label": { "en": "Blue" } },
"size": { "key": "l", "label": "L" }
}
}
]
}
Use this response to build your attribute selectors and to deactivate unavailable combinations without loading the full product payload or making multiple API calls for each Variant.
Add the Variant to a Cart
variantId or sku when adding a line item. Specifying productId alone is not sufficient.sku:curl -X POST "https://api.europe-west1.gcp.commercetools.com/{project-key}/carts" \
-H "Authorization: Bearer {auth-token}" \
-H "Content-Type: application/json" \
-d '{
"currency": "EUR",
"lineItems": [
{
"sku": "JACKET-RED-M",
"quantity": 1
}
]
}'
productId and variantId:curl -X POST "https://api.europe-west1.gcp.commercetools.com/{project-key}/carts" \
-H "Authorization: Bearer {auth-token}" \
-H "Content-Type: application/json" \
-d '{
"currency": "EUR",
"lineItems": [
{
"productId": "{product-id}",
"variantId": 1,
"quantity": 1
}
]
}'
Only published Variants can be added to a Cart. Attempting to add an unpublished Variant returns an error.
JACKET-RED-M with the correct quantity.Create the Order
Once the customer confirms their purchase, create an Order from the Cart:
curl -X POST "https://api.europe-west1.gcp.commercetools.com/{project-key}/orders" \
-H "Authorization: Bearer {auth-token}" \
-H "Content-Type: application/json" \
-d '{
"cart": { "typeId": "cart", "id": "{cart-id}" },
"version": 1
}'
201 Created response confirms the Order has been placed. The line items reference the Variant via the sku or productId and variantId combination used when adding the item to the Cart.Conclusion
You have learned how to:
- Enable Modular mode on a commercetools Project.
- Create a Product and add Variants with attributes, images, and SKUs using the Variants endpoint.
- Publish individual Variants independently of the parent Product.
- Use the Variant Projections endpoint to retrieve full Variant data (including price) for a PDP.
- Use the Variant Attributes endpoint to efficiently build attribute selectors for all Variants of a Product.
- Add a Variant to a Cart using
skuorvariantId, and create an Order.
Further reading
- API reference for the Variants, Variant Projections, and Variant Attributes endpoints.
- API reference for Standalone Prices for how to create and manage prices linked to Variant SKUs in Modular mode.