The Cart is the single source of truth for all checkout calculations in Composable Commerce. Before a customer proceeds to shipping, payment, and Order finalization, the Cart must be up to date.
- The Cart must be linked to a user: For guest users, the
anonymousIdis set on the Cart. For registered users, thecustomerIdis set on the Cart. - Prices, promotions, and totals must be up to date: The Cart totals (subtotal, taxes, shipping, discounts) must be accurate. When you change item quantities or apply a discount code, Composable Commerce automatically recalculates prices. To update a stale Cart, trigger a recalculate action manually.
- Custom business rule validations must be enforced: Composable Commerce prevents invalid actions like creating an Order with a deleted Product. However, you are responsible for implementing most business rule validations, such as minimum order values or item stock requirements.
At the review stage, resolve any discrepancies to avoid issues during checkout—for example, if a discount is no longer valid or a cart is empty, resolve the issue before the customer proceeds to payment. Such "sanity checks" ensure the Cart contents are valid and ready for checkout.
Retrieving the active Cart
Cart retrieval differs for registered and guest (anonymous) users. In both cases, use a single active Cart for the session to reduce complexity, even though Composable Commerce technically allows multiple active Carts per customer.
Guest (anonymous) users
anonymousId, which is an arbitrary identifier (like a UUID) that you store in the user's browser. When creating a Cart for the guest, set the anonymousId so you can query it later.Registered users
customerId, which is associated with a Customer account. If a Customer has multiple active Carts, merge them or select the most recent one, based on your business requirements.Merging Carts on login
If a user begins as a guest and then logs in, they might end up with two Carts: the guest Cart and the Cart tied to their account. To avoid losing items or discounts, define merge rules—for example, prioritize the customer's Cart or keep the guest Cart, based on your business requirements.
Displaying Cart contents
When displaying the Cart in the UI, map the API response to only the fields your frontend requires. This prevents over-fetching and unnecessary data transfer. You can achieve this effectively by using the GraphQL API to query the key fields.
The following are key fields for a Cart summary view:
- Line Items: Map to
cart.lineItems. It is an array of Products, and each Line Item includes the Productname,variant,quantity, per-unitprice, andtotalPricefor that line, which accounts for quantity and any line item-specific discounts. - Discounts: Map to
cart.discountCodes, if discount codes are applied. To display the code string, expand the reference. - Taxed Price: Map to
cart.taxedPrice, if taxes are configured. It provides a breakdown of the net, gross, and tax portions of the price. - Shipping Info: Map to
cart.shippingInfo, if a shipping method is set. It contains the method name and cost, which you can display in the order summary.
Cart modifications (pre-checkout)
Users often make last-minute changes during the cart review stage; common modifications include changing item quantities, removing items, and applying or removing discount codes.
changeLineItemQuantity: updates the quantity of a Line ItemremoveLineItem: removes a Line Item from the CartaddDiscountCodeorremoveDiscountCode: adds or removes a discount code
anonymousId or customerId matches the current user. This "read-before-write" pattern prevents conflicting updates.async function applyDiscountCode(
storeKey: string,
cartId: string,
version: number,
code: string
) {
return apiRoot
.inStoreKeyWithStoreKeyValue({ storeKey })
.carts()
.withId({ ID: cartId })
.post({
body: {
version,
actions: [{ action: "addDiscountCode", code }],
},
})
.execute();
}
Handling discount code failures
Discount codes fail in two ways:
- Immediate failure: If you call
addDiscountCodewith an invalid code or if the Cart does not meet the promotion's conditions, Composable Commerce returns an error. Alternatively, the discount code appears indiscountCodeswith aDoesNotMatchCartstate. - Delayed invalidation: A discount can become invalid after being applied. This can happen if the user removes an item required for the discount, the promotion expires, or a usage limit is reached. Composable Commerce re-evaluates discount validity on every Cart update.
When a discount code is invalid or has expired, provide clear, user-friendly feedback to help users correct the issue. Explain why the code failed instead of a generic error message—for example, "This code has expired" or "This code is not valid for the items in your cart."
discountCodes state after every Cart modification. If a discount code state is not MatchesCart, remove the discount code and inform the user. This ensures the final price is accurate before payment.Handling guest checkout limitations
While guest checkouts reduce friction, they have limitations—for example, since a guest is not associated with a Customer Group, Cart Discounts that target specific groups won't apply automatically.
You can work around this limitation using one of the following strategies:
- Set a Custom Field on the Cart: Configure the Cart Discount to check a Custom Field on the Cart instead of the customer's
customerGroup. This allows you to apply targeted discounts to guest users without requiring them to register. - Create a placeholder Customer: Create a placeholder Customer entity in Composable Commerce that contains a link to the external customer, identifiable through a unique identifier. This allows you to associate the guest with a Customer Group.
Validating the Cart
Before proceeding to checkout, ensure the Cart contains all necessary data and meets your business requirements.
Required Cart data before checkout
The Cart must have the following data before the user can proceed to the shipping stage:
- At least one
lineItem - A
currencyandcountry(the country is important for tax calculations) - A valid, non-zero
totalPrice(unless you allow free Orders) - All prices recalculated after the last update
Custom business rule validations
Beyond Composable Commerce built-in checks, you might need to implement custom validations:
- Minimum or maximum order values: For example, requiring a Cart total of at least US$20.
- Cart not empty: Prevent users from proceeding to checkout with an empty Cart.
- Inventory availability: By default, Composable Commerce does not reserve stock when a Cart is created. Depending on the Cart
inventoryMode, stock is only checked and decremented when the Order is created. You might want to check commercetools inventories on add to Cart and perform a real-time stock check before payment to inform the user if an item has gone out of stock. The ultimate check occurs at Order creation, which fails if stock is insufficient inReserveOnOrdermode. - Shipping eligibility or other business rules: For example, restricting shipment of certain items to specific regions.
function validateCart(cart) {
if (cart.totalPrice.centAmount < 2000) { // e.g. $20
throw new Error('Minimum order value is $20. Add more items to your Cart');
}
if (!cart.lineItems.length) {
throw new Error('Cart is empty');
}
// Example, if using inventoryMode = ReserveOnOrder, optionally check stock
for (const item of cart.lineItems) {
if (item.variant.availability.availableQuantity !== undefined && item.variant.availability.availableQuantity < item.quantity) {
throw new Error(`Not enough stock for item ${item.name.en}. Available: ${item.variant.availability.availableQuantity}`);
}
}
}
availableQuantity is not a default field on Line Items. To perform a stock check, expand inventory data or manage it separately.Use BFF and API Extensions for cross-system validation
Use your BFF for most validations. Categorize your validations into two groups: early blockers and late critical checks. Check early blockers (like an empty Cart) on the Cart page for immediate feedback. Perform critical checks (like final inventory) just before Order creation.
In some cases, API Extensions are appropriate for server-side validations, such as checking inventory on Order creation. Use them when:
- There is no BFF, API gateway, or proxy in place.
- There is a need to intercept changes applied in the Merchant Center (like placing an order on behalf of a customer) and there is no custom application in place.
- There are many touchpoints in play (like POS, webshop, and mobile) that go through the same validation logic and the performance of the logic can be guaranteed.
- You are building a third-party integration (like with PSPs and discounting engines) that is frontend agnostic and can be used by many different projects or customers.
Implementation example: Cart retrieval and validation flow
The following diagram shows how the client, BFF, and Composable Commerce interact during Cart retrieval and validation:
Key takeaways
- Maintain a single active Cart per session to simplify the user experience.
- Fetch the Cart after every modification to get the latest version and prevent conflicts.
- Define Cart merge rules for users who transition from guest to registered (logged-in) status.
- Use the Cart as the single source of truth for pricing, promotions, and items.
- Validate discount codes after every Cart modification to ensure they remain applicable.
- Implement explicit validation logic for business rules at early and late stages of checkout.
- Ensure the Cart contains all required data (line items, currency, country, and total price) before proceeding to checkout.
Next, we will proceed to shipping information, where you'll learn to collect and validate addresses, select shipping methods, and update Cart totals accordingly.