# Model and sell products with Modular Catalog 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. ```curl title="Enable Modular mode" highlightLines="8,9" 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](/api/projects/variants.md) endpoint. ```curl title="Create a product" 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 title="Create a Variant" 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"`. ```json title="Successful response" { "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`: ```curl title="Update staged data" highlightLines="13" 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 title="Publish a Variant" 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. ```curl title="Fetch the selected Variant" 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: ```json title="Successful response" { "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 title="Fetch all Variants" 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: ```json title="Successful response" { "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](/api/projects/variant-projections.md) 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`: ```curl title="Add the SKU to a Cart" 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`: ```curl title="Add the Variant to a Cart" 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: ```curl title="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 }' ``` 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 - API reference for the [Variants](/api/projects/variants.md), [Variant Projections](/api/projects/variant-projections.md), and [Variant Attributes](/api/projects/variant-attributes.md) endpoints. - API reference for [Standalone Prices](/api/projects/standalone-prices.md) for how to create and manage prices linked to Variant SKUs in Modular mode. ## Related pages - [Area overview page with navigation](/tutorials.md) - [Previous page: Model assortments with Product Selections](/tutorials/product-selections.md) - [Search documentation and API specs](/search.md)