Skip to main content

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:

FieldTypeDescription
productIdInt!Product to add (required)
quantityIntNumber of units. Defaults to 1
clusterIdIntCluster the product belongs to, if applicable
notesStringItem-level notes (e.g., special instructions)
priceFloatCustom 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.

FieldDescription
subTotalSum of all items, excluding shipping, payment costs and discounts. Excluding VAT
subTotalNetSame as above, including VAT
totalGrossFinal payable amount, excluding VAT
totalNetFinal payable amount, including VAT
discountDiscount applied through incentives, excluding VAT
discountNetDiscount applied through incentives, including VAT
discountPercentageDiscount 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 price field on items is always excluding VAT and priceNet is including VAT. This convention is consistent across the entire API.

Understanding item prices

Each cart item has several price fields:

FieldDescription
price / priceNetUnit price excluding / including VAT
totalPrice / totalPriceNetTotal for this item after item-specific discounts
sum / sumNetUnit price including child item prices, before item discounts
totalSum / totalSumNetFinal 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:

FieldTypeDescription
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)
createdAtDateSearchInputFilter by creation date range
lastModifiedAtDateSearchInputFilter by last modification date range
pageIntPage number (defaults to 1)
offsetIntItems 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:

  1. Retrieve the guest cartId from localStorage
  2. Query the authenticated user's carts with the carts query
  3. If the user has an existing cart, merge items using cartAddItem on the authenticated cart. Alternatively, associate the guest cart with the user using cartSetContact or cartSetCustomer
  4. Update localStorage with the active cartId

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:

ErrorCause
Cart not foundInvalid or expired cartId
Product not foundInvalid productId
Cart item not foundInvalid itemId
Pricing information not foundProduct has no price configured
Invalid action codeDiscount code does not exist or has expired

Next steps