Cart management
Create and manage shopping carts using Propeller's GraphQL API. Add items, update quantities, apply discount codes, fetch totals and handle cart persistence across sessions. Propeller calculates all pricing, discounts and taxes server-side.
Creating a cart
Use cartStart to create a new cart. The mutation returns the full Cart object including a cartId (UUID) that you need for all subsequent operations.
For a B2B contact:
mutation {
cartStart(
input: {
contactId: 312
companyId: 456
}
) {
cartId
contactId
companyId
createdAt
}
}
Expected response:
{
"data": {
"cartStart": {
"cartId": "019abc12-3456-7def-8901-234567890abc",
"contactId": 312,
"companyId": 456,
"createdAt": "2026-02-27T10:15:00.000Z"
}
}
}
For a B2C customer:
mutation {
cartStart(
input: {
customerId: 789
}
) {
cartId
customerId
createdAt
}
}
For a guest user:
mutation {
cartStart {
cartId
createdAt
}
}
Omit all IDs to create a guest cart. You can associate it with a user later using cartSetContact or cartSetCustomer (see Changing cart ownership).
Store the cartId in your application state or localStorage. Every cart operation requires it.
Adding items
Use cartAddItem to add a product to the cart:
mutation {
cartAddItem(
id: "019abc12-3456-7def-8901-234567890abc"
input: {
productId: 104708
quantity: 3
}
) {
items {
itemId
productId
quantity
price
priceNet
totalPrice
totalPriceNet
}
total {
subTotal
subTotalNet
totalGross
totalNet
}
}
}
Expected response:
{
"data": {
"cartAddItem": {
"items": [
{
"itemId": "019abc13-1a2b-7c3d-4e5f-678901234567",
"productId": 104708,
"quantity": 3,
"price": 24.50,
"priceNet": 29.65,
"totalPrice": 73.50,
"totalPriceNet": 88.94
}
],
"total": {
"subTotal": 73.50,
"subTotalNet": 88.94,
"totalGross": 73.50,
"totalNet": 88.94
}
}
}
}
The input accepts these fields:
| Field | Type | Description |
|---|---|---|
productId | Int! | Product to add (required) |
quantity | Int | Number of units. Defaults to 1 |
clusterId | Int | Cluster the product belongs to, if applicable |
notes | String | Item-level notes (e.g., special instructions) |
price | Float | Custom unit price. Overrides platform pricing when set |
childItems | [CartChildItemInput!] | Child items for configurable clusters |
The response includes an itemId for each cart item. You need this ID for updating or deleting the item later.
If the product has minimumQuantity or purchaseUnit constraints, the API rejects quantities that violate these rules.
Adding items with child items
For configurable clusters where a main product has selectable options (e.g., a workstation with monitors and peripherals), pass childItems:
mutation {
cartAddItem(
id: "019abc12-3456-7def-8901-234567890abc"
input: {
productId: 104900
clusterId: 2150
quantity: 1
childItems: [
{ productId: 104901, quantity: 1 }
{ productId: 104902, quantity: 2 }
]
}
) {
items {
itemId
productId
quantity
price
priceNet
childItems {
itemId
productId
quantity
price
priceNet
}
}
}
}
Expected response:
{
"data": {
"cartAddItem": {
"items": [
{
"itemId": "019abc15-3c4d-7e5f-6071-890123456789",
"productId": 104900,
"quantity": 1,
"price": 500,
"priceNet": 605,
"childItems": [
{
"itemId": "019abc15-3c4d-7e5f-6071-890123456790",
"productId": 104901,
"quantity": 1,
"price": 0,
"priceNet": 0
},
{
"itemId": "019abc15-3c4d-7e5f-6071-890123456791",
"productId": 104902,
"quantity": 2,
"price": 0,
"priceNet": 0
}
]
}
]
}
}
}
Each child item accepts productId (required), quantity (defaults to 1), notes and price.
Adding bundles
Use cartAddBundle to add a predefined product bundle:
mutation {
cartAddBundle(
id: "019abc12-3456-7def-8901-234567890abc"
input: {
bundleId: "019c9425-5eaf-72b5-9315-d7182932db45"
quantity: 1
}
) {
items {
itemId
productId
bundleId
quantity
price
priceNet
}
}
}
Expected response:
{
"data": {
"cartAddBundle": {
"items": [
{
"itemId": "019ca0f6-faba-7e17-88fd-afb653794297",
"productId": 2017,
"bundleId": "019c9425-5eaf-72b5-9315-d7182932db45",
"quantity": 1,
"price": 270.885,
"priceNet": 327.77
}
]
}
}
}
The input accepts bundleId (required), quantity (defaults to 1) and notes.
Bulk item operations
Use cartItemBulk to add or update multiple items in a single call:
mutation {
cartItemBulk(
input: {
cartId: "019abc12-3456-7def-8901-234567890abc"
items: [
{ productId: 104708, quantity: 5 }
{ productId: 104923, quantity: 2 }
{ itemId: "019abc13-1a2b-7c3d-4e5f-678901234567", quantity: 10 }
]
}
) {
created
updated
total
}
}
Expected response:
{
"data": {
"cartItemBulk": {
"created": 2,
"updated": 1,
"total": 3
}
}
}
Unlike other cart mutations, the cartId is inside the input object. Each item can use productId to create a new line or itemId to update an existing one.
cartItemBulk is particularly useful for B2B workflows like reordering, where a buyer adds dozens of items at once from a previous order or an imported product list.
Updating items
Use cartUpdateItem to change the quantity, notes or price of an existing item. Note the three separate arguments: id (cart), itemId (item) and input:
mutation {
cartUpdateItem(
id: "019abc12-3456-7def-8901-234567890abc"
itemId: "019abc13-1a2b-7c3d-4e5f-678901234567"
input: {
quantity: 5
notes: "Deliver to warehouse B"
}
) {
items {
itemId
productId
quantity
notes
price
priceNet
totalPrice
totalPriceNet
}
total {
totalGross
totalNet
}
}
}
Expected response:
{
"data": {
"cartUpdateItem": {
"items": [
{
"itemId": "019abc13-1a2b-7c3d-4e5f-678901234567",
"productId": 104708,
"quantity": 5,
"notes": "Deliver to warehouse B",
"price": 24.50,
"priceNet": 29.65,
"totalPrice": 122.50,
"totalPriceNet": 148.23
}
],
"total": {
"totalGross": 122.50,
"totalNet": 148.23
}
}
}
}
The input accepts quantity (defaults to 1), notes and price (custom unit price override).
Removing items
Use cartDeleteItem to remove an item from the cart:
mutation {
cartDeleteItem(
id: "019abc12-3456-7def-8901-234567890abc"
input: {
itemId: "019abc13-1a2b-7c3d-4e5f-678901234567"
}
) {
items {
itemId
productId
quantity
}
total {
totalGross
totalNet
}
}
}
Expected response:
{
"data": {
"cartDeleteItem": {
"items": [],
"total": {
"totalGross": 0,
"totalNet": 0
}
}
}
}
Fetching cart state
Use the cart query to retrieve the full cart at any point:
query {
cart(id: "019abc12-3456-7def-8901-234567890abc") {
cartId
contactId
companyId
customerId
notes
reference
actionCode
status
createdAt
lastModifiedAt
total {
subTotal
subTotalNet
totalGross
totalNet
discount
discountNet
discountPercentage
}
taxLevels {
taxPercentage
price
}
items {
itemId
productId
clusterId
quantity
notes
price
priceNet
totalPrice
totalPriceNet
sum
sumNet
totalSum
totalSumNet
discount
discountPercentage
taxCode
childItems {
itemId
productId
quantity
price
priceNet
}
}
bonusItems {
itemId
productId
quantity
totalPrice
totalPriceNet
}
}
}
Expected response:
{
"data": {
"cart": {
"cartId": "019abc12-3456-7def-8901-234567890abc",
"contactId": 312,
"companyId": 456,
"customerId": null,
"notes": "",
"reference": "PO-2026-0042",
"actionCode": "",
"status": "OPEN",
"createdAt": "2026-02-27T10:15:00.000Z",
"lastModifiedAt": "2026-02-27T11:30:00.000Z",
"total": {
"subTotal": 196.00,
"subTotalNet": 237.16,
"totalGross": 196.00,
"totalNet": 237.16,
"discount": 0,
"discountNet": 0,
"discountPercentage": 0
},
"taxLevels": [
{
"taxPercentage": 21,
"price": 41.16
}
],
"items": [
{
"itemId": "019abc13-1a2b-7c3d-4e5f-678901234567",
"productId": 104708,
"clusterId": null,
"quantity": 5,
"notes": "Deliver to warehouse B",
"price": 24.50,
"priceNet": 29.65,
"totalPrice": 122.50,
"totalPriceNet": 148.23,
"sum": 24.50,
"sumNet": 29.65,
"totalSum": 122.50,
"totalSumNet": 148.23,
"discount": 0,
"discountPercentage": 0,
"taxCode": "H",
"childItems": []
},
{
"itemId": "019abc14-2b3c-7d4e-5f60-789012345678",
"productId": 104923,
"clusterId": null,
"quantity": 2,
"notes": "",
"price": 36.75,
"priceNet": 44.47,
"totalPrice": 73.50,
"totalPriceNet": 88.94,
"sum": 36.75,
"sumNet": 44.47,
"totalSum": 73.50,
"totalSumNet": 88.94,
"discount": 0,
"discountPercentage": 0,
"taxCode": "H",
"childItems": []
}
],
"bonusItems": []
}
}
}
The cart also has fields for invoiceAddress, deliveryAddress, paymentData, postageData, payMethods, carriers and shippingMethods. These are used during checkout and documented in Checkout flow.
Understanding totals
Propeller calculates all totals server-side. You do not need to compute anything in your frontend.
| Field | Description |
|---|---|
subTotal | Sum of all items, excluding shipping, payment costs and discounts. Excluding VAT |
subTotalNet | Same as above, including VAT |
totalGross | Final payable amount, excluding VAT |
totalNet | Final payable amount, including VAT |
discount | Discount applied through incentives, excluding VAT |
discountNet | Discount applied through incentives, including VAT |
discountPercentage | Discount percentage applied through incentives |
The taxLevels array breaks down tax amounts per tax rate. This is useful when multiple VAT rates apply (e.g., standard 21% and reduced 9%).
Naming convention: Throughout the cart API, "Net" means including VAT and "Gross" means excluding VAT. The
pricefield on items is always excluding VAT andpriceNetis including VAT. This convention is consistent across the entire API.
Understanding item prices
Each cart item has several price fields:
| Field | Description |
|---|---|
price / priceNet | Unit price excluding / including VAT |
totalPrice / totalPriceNet | Total for this item after item-specific discounts |
sum / sumNet | Unit price including child item prices, before item discounts |
totalSum / totalSumNet | Final total including child items, quantity and discounts |
For items without child items, price equals sum and totalPrice equals totalSum. The sum fields become relevant when you use configurable clusters with child items.
Applying discount codes
Use cartAddActionCode to apply a discount code (called an "action code" in Propeller):
mutation {
cartAddActionCode(
id: "019abc12-3456-7def-8901-234567890abc"
input: {
actionCode: "EXTRASALE10"
}
) {
actionCode
total {
subTotal
subTotalNet
totalGross
totalNet
discount
discountNet
discountPercentage
}
}
}
Expected response:
{
"data": {
"cartAddActionCode": {
"actionCode": "EXTRASALE10",
"total": {
"subTotal": 270.885,
"subTotalNet": 327.77,
"totalGross": 315.885,
"totalNet": 382.22,
"discount": 10,
"discountNet": 12.10,
"discountPercentage": 3.69
}
}
}
}
The actionCode field on the cart is only filled when the code is valid. If the code does not exist or has expired, the mutation returns an error.
Remove a discount code with cartRemoveActionCode:
mutation {
cartRemoveActionCode(
id: "019abc12-3456-7def-8901-234567890abc"
input: {
actionCode: "EXTRASALE10"
}
) {
actionCode
total {
totalGross
totalNet
discount
discountPercentage
}
}
}
Expected response:
{
"data": {
"cartRemoveActionCode": {
"actionCode": "",
"total": {
"totalGross": 325.885,
"totalNet": 394.32,
"discount": 0,
"discountPercentage": 0
}
}
}
}
Both mutations use the same input type (CartActionCodeInput).
Updating cart details
Use cartUpdate to set notes, a reference number or custom fields on the cart:
mutation {
cartUpdate(
id: "019abc12-3456-7def-8901-234567890abc"
input: {
notes: "Please deliver before 14:00"
reference: "PO-2026-0042"
}
) {
notes
reference
}
}
Expected response:
{
"data": {
"cartUpdate": {
"notes": "Please deliver before 14:00",
"reference": "PO-2026-0042"
}
}
}
The input also accepts extra3 and extra4 for additional custom data. All of these fields persist to the order after checkout.
The reference field is typically used by B2B buyers to store their internal purchase order (PO) number. Companies require PO numbers on orders so they can match invoices to approved purchase requests in their financial system. For B2C, this field is rarely used.
The cartUpdate mutation is also used for setting shipping and payment data during checkout. See Checkout flow for those operations.
Changing cart ownership
To associate a guest cart with a user after login, use cartSetContact (B2B) or cartSetCustomer (B2C):
mutation {
cartSetContact(
id: "019abc12-3456-7def-8901-234567890abc"
input: {
contactId: 312
companyId: 456
}
) {
cartId
contactId
companyId
}
}
mutation {
cartSetCustomer(
id: "019abc12-3456-7def-8901-234567890abc"
input: {
customerId: 789
}
) {
cartId
customerId
}
}
For cartSetContact, the companyId is optional and defaults to the contact's parent company.
Deleting a cart
Use cartDelete to permanently remove a cart:
mutation {
cartDelete(id: "019abc12-3456-7def-8901-234567890abc")
}
Expected response:
{
"data": {
"cartDelete": true
}
}
Returns true on success.
Cart persistence
Guest users
For unauthenticated users, store the cartId locally after calling cartStart. After calling cartStart, store the returned cartId in browser storage (for example localStorage). This is the only value you need to persist on the client side.
On page load, retrieve the stored cartId and use it with the cart query to restore the cart state.
After order completion, clear the stored cartId.
Cart items persist server-side in Propeller. Only the cartId reference needs to be stored locally. Checkout data (addresses, payment, shipping) is retained for 30 days.
Authenticated users
For logged-in users, retrieve all their carts with the carts query:
query {
carts(
input: {
contactIds: [312]
companyIds: [456]
}
) {
items {
cartId
createdAt
lastModifiedAt
items {
itemId
productId
quantity
}
total {
totalGross
totalNet
}
}
itemsFound
page
pages
}
}
Expected response:
{
"data": {
"carts": {
"items": [
{
"cartId": "019abc12-3456-7def-8901-234567890abc",
"createdAt": "2026-02-27T10:15:00.000Z",
"lastModifiedAt": "2026-02-27T11:30:00.000Z",
"items": [
{
"itemId": "019abc13-1a2b-7c3d-4e5f-678901234567",
"productId": 104708,
"quantity": 5
}
],
"total": {
"totalGross": 122.50,
"totalNet": 148.23
}
}
],
"itemsFound": 1,
"page": 1,
"pages": 1
}
}
}
The CartSearchInput supports these filters:
| Field | Type | Description |
|---|---|---|
ids | [String!] | Filter by specific cart IDs |
contactIds | [Int!] | Filter by contact IDs |
customerIds | [Int!] | Filter by customer IDs |
companyIds | [Int!] | Filter by company IDs |
statuses | [CartStatus!] | Filter by cart status (OPEN or PENDING_PURCHASE_AUTHORIZATION) |
createdAt | DateSearchInput | Filter by creation date range |
lastModifiedAt | DateSearchInput | Filter by last modification date range |
page | Int | Page number (defaults to 1) |
offset | Int | Items per page (defaults to 12) |
sortInputs | [CartSortInput!] | Sort by ID, LAST_MODIFIED_AT or CREATED_AT |
Guest-to-authenticated transition
When a guest user logs in, you may need to merge their guest cart with an existing authenticated cart:
- Retrieve the guest
cartIdfromlocalStorage - Query the authenticated user's carts with the
cartsquery - If the user has an existing cart, merge items using
cartAddItemon the authenticated cart. Alternatively, associate the guest cart with the user usingcartSetContactorcartSetCustomer - Update
localStoragewith the activecartId
Multiple carts
Propeller supports multiple active carts per user. Each cart has its own cartId and operates independently. In B2B, buyers often manage separate carts for different departments, cost centers or projects within their organization. Each cart maps to a different budget line or internal approval path. Common use cases include:
- Project carts for different departments or purchase orders
- Saved carts that users want to complete later
- Quick-order carts for recurring purchases
Create additional carts with cartStart and switch between them by using the appropriate cartId in your queries and mutations. Use the carts query to list all carts for a user.
Error handling
Cart mutations return errors in the GraphQL response errors array. Common errors include:
| Error | Cause |
|---|---|
| Cart not found | Invalid or expired cartId |
| Product not found | Invalid productId |
| Cart item not found | Invalid itemId |
| Pricing information not found | Product has no price configured |
| Invalid action code | Discount code does not exist or has expired |
Next steps
- Checkout flow for setting addresses, shipping, payment and creating the order
- Customer-specific pricing for how price sheets affect cart totals
- Add a product to cart for a ready-to-use recipe
- Fetch cart with totals for a ready-to-use recipe