Before creating a new Cart, it is a crucial best practice to check if the customer (anonymous or logged-in) already has an active one. This prevents duplicate Carts, reduces data clutter, and allows customers to seamlessly resume their shopping session. Most commerce sites enforce a "one active Cart per customer" rule to simplify the user experience.
Construct the optimal query
Step 1: Choose the right endpoint
| Endpoint | When to use |
|---|---|
Project-scoped: /carts | Use this when your application does not use Stores, or when you need to search across Store contexts, without knowing the exact Store the customer's Cart belongs to. You can optionally filter by store using a where predicate. |
Store-scoped: /in-store/key={storeKey}/carts | Use this when the Store context is already known. This avoids an extra Store predicate and matches Store-restricted API Clients well. |
/carts endpoint. It provides the most flexibility.Step 2: Build the where predicate
where predicate is the most critical part of your query. For best performance, order the clauses from most to least selective.| Predicate component | Why it matters |
|---|---|
customerId="<id>" or anonymousId="<id>" | Identifies the owner of the Cart. This is the primary and most selective filter. |
cartState in ("Active", "Frozen") | Restricts the result to Carts that are still relevant to the shopping session. It excludes no longer modifiable Carts (Ordered, Merged). |
store(key="<store-key>") | Adds Store isolation only when you query the /carts endpoint in a multi-Store setup. |
where predicate:where=customerId="c123-..." AND cartState in ("Active", "Frozen") AND store(key="zen-electron-us")Step 3: Add performance and consistency parameters
Use only the query parameters that support your retrieval rule and improve performance.
| Parameter | Recommended value | Purpose |
|---|---|---|
sort | lastModifiedAt desc | Makes the selection deterministic when more than one active Cart exists. By combining it with limit=1, you are guaranteed to retrieve the most recently updated customer Cart. |
limit | 1 | Enforces the common "one active Cart per customer" rule and stops the query after finding the first match, improving thus performance. |
withTotal | false | Skips unnecessary total-count calculation when you only need the top result. This can significantly speed up the API response and improve performance. |
Complete API request example
zen-electron-us Store:GET /carts?limit=1&withTotal=false&sort=lastModifiedAt desc&where=customerId="c123-..." AND cartState in ("Active", "Frozen") AND store(key="zen-electron-us")
Handling the response:
- If a Cart is found: the API will return a
resultsarray with one Cart object. Your application should use this Cart. - If no Cart is found: the API will return an empty
resultsarray. Your application should proceed to create a new Cart when the user adds their first item.
Implementation with the SDK
Here is how you can implement this optimal query strategy using the TypeScript SDK.
For an anonymous user:
import { apiRoot } from "../../client";
import { Cart } from "@commercetools/platform-sdk";
async function getActiveAnonymousCart(
storeKey: string,
anonymousId: string
): Promise<Cart | undefined> {
try {
const { body } = await apiRoot
.inStoreKeyWithStoreKeyValue({ storeKey })
.carts()
.get({
queryArgs: {
limit: 1,
withTotal: false,
sort: "lastModifiedAt desc",
where: [`anonymousId="${anonymousId}"`, `cartState="Active"`],
},
})
.execute();
return body.results[0]; // Returns the cart or undefined if not found
} catch (error) {
console.error("Error fetching anonymous cart:", error);
throw error; // Rethrow or handle appropriately
}
}
For a logged-in customer:
import { apiRoot } from "../../client";
import { Cart } from "@commercetools/platform-sdk";
async function getActiveCustomerCart(
storeKey: string,
customerId: string
): Promise<Cart | undefined> {
try {
const { body } = await apiRoot
.inStoreKeyWithStoreKeyValue({ storeKey })
.carts()
.get({
queryArgs: {
limit: 1,
withTotal: false,
sort: "lastModifiedAt desc",
where: [`customerId="${customerId}"`, `cartState="Active"`],
},
})
.execute();
return body.results[0]; // Returns the cart or undefined if not found
} catch (error) {
console.error("Error fetching customer cart:", error);
throw error; // Rethrow or handle appropriately
}
}
How to enforce a single active Cart
limit=1) before creating a new Cart, you ensure that you always reuse an existing active Cart if one is found.How to handle multiple Carts
While a single active Cart is the most common pattern, some business models might require multiple active Carts per user (for example, a B2B "projects" Cart and a "personal" Cart).
If your business logic allows multiple active Carts, modify your query strategy:
- Remove
limit=1: allow the query to return all active Carts. - Use
sort: ensure results are consistently ordered (for example,lastModifiedAt desc). - Implement selection logic: your application must provide a way for the user to view and select which Cart they want to interact with.
async function getAllActiveCustomerCarts(
storeKey: string,
customerId: string
): Promise<Cart[]> {
try {
const { body } = await apiRoot
.inStoreKeyWithStoreKeyValue({ storeKey })
.carts()
.get({
queryArgs: {
sort: "lastModifiedAt desc",
where: [`customerId="${customerId}"`, `cartState="Active"`],
},
})
.execute();
return body.results; // Returns an array of all active carts
} catch (error) {
console.error("Error fetching all customer carts:", error);
return [];
}
}
Use Custom Fields to differentiate Carts
- Define a Custom Type: create a Custom Type for Carts (for example,
cart-attributes) with a field likecartType(of typeEnumorString). - Set the Custom Field value on creation: when creating a Cart, populate the Custom Field.
const cartDraft: CartDraft = {
customerId: customerId,
currency: "USD",
country: "US",
custom: {
type: { key: "cart-attributes", typeId: "type" },
fields: {
cartType: "B2B-Project-A",
},
},
};
// ... post the cartDraft
- Query by Custom Field: add the custom field to your
wherepredicate to find specific types of Carts.
const where = [
`customerId="${customerId}"`,
`cartState="Active"`,
`custom(fields(cartType="B2B-Project-A"))`,
];