Model and sell products with Modular Catalog

Ask about this Page
Copy for LLM
View as Markdown

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 Classic model: where Product Variants are embedded within the Product. This model is suitable for use cases with up to 100 Variants per Product.
  • The Modular model: 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 Modular model 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.

You can switch freely between Modular and Classic mode. This tutorial assumes a new Project or environment with no existing embedded Variants.
Enable Modular modeshell
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"
      }
    ]
  }'

Verify the change by querying the Project settings. In the response, confirm that 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.

In modular mode, you cannot include Variant data (masterVariant, variants) when creating a Product. Create the Product first, then add Variants using the Variants endpoint.
Create a productshell
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:

Create a Variantshell
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
  }'
The response confirms the Variant was created in an unpublished state ("published": false). The staged field is null because no staged changes exist yet and the Variant data is available in "current".
Successful responsejson
{
  "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
}
Repeat this step for each additional Variant (for example, JACKET-RED-L, JACKET-BLUE-M, and so on). Each Variant gets its own ID and an auto-incremented variantId that is unique within the Product.

Update the Variant's staged data

Before publishing, you can update staged data without affecting the published state. Use the setAttributes update action with "staged": true:
Update staged datashell
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.

Publish a Variantshell
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" }
    ]
  }'
In the response, "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.

Fetch the selected Variantshell
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}"
A successful 200 response merges Product-level and Variant-level data into a single object, ready for the PDP:
Successful responsejson
{
  "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.

Fetch all Variantsshell
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.
The successful 200 response returns a slim payload for every published Variant, with availability included:
Successful responsejson
{
  "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.

Price information is not included in the Variant Attributes API response. Use the Variant Projections endpoint for prices.

Add the Variant to a Cart

Once the customer selects a Variant and proceeds to the checkout, add it to a Cart. In Modular mode, you must specify either a variantId or sku when adding a line item. Specifying productId alone is not sufficient.
Create a Cart and add the red M jacket by sku:
Add the SKU to a Cartshell
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
      }
    ]
  }'
Alternatively, reference the Variant by productId and variantId:
Add the Variant to a Cartshell
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.

In the response, verify that the lineItems array contains an entry for JACKET-RED-M with the correct quantity.

Create the Order

Once the customer confirms their purchase, create an Order from the Cart:

Create an Order from the Cartshell
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
  }'
A successful 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 sku or variantId, and create an Order.

Further reading