Skip to main content

Cart Events

These events handle shopping cart interactions including viewing, updating, validating, adding, and removing items.

cart:update

Dispatched by store when the cart content changes.

Example
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
nube.on("cart:update", ({ cart }) => {
if (cart.items.length > 5) {
console.log("Purchased more than 5 different items");
}
});
}

cart:before_update

Storefront only. Dispatched by store before any cart line mutation — adding an item, editing an item's quantity, or removing an item. It lets an app confirm or cancel the operation before the cart changes.

This event is opt-in. It is only dispatched if at least one installed app enables handle_cart_before_update through config:set :

Opting in
nube.send("config:set", () => ({
config: {
handle_cart_before_update: true,
},
}));

If no installed app opted in, no event is dispatched and the cart mutation runs immediately, with no added latency.

eventPayload

PropertyTypeDescription
request_idstringUnique identifier for this request. Must be echoed back in cart:before_update:result.
action"ADD" or "REMOVE"Derived from the quantities: ADD when new_quantity > previous_quantity, REMOVE otherwise.
itemobjectThe cart line about to change. See the table below.

The item object:

PropertyTypeDescription
product_idnumber or nullIdentifier of the product. null when the storefront cannot resolve it.
variant_idnumber or nullIdentifier of the selected variant. null when the storefront cannot resolve it.
previous_quantitynumberQuantity of this item in the cart before the operation.
new_quantitynumberQuantity it would have after the operation. 0 when the item is being removed.
Quantity edits

Editing a quantity in the cart is reported as ADD or REMOVE according to the direction of the change — there is no separate action for it.

Responding to the request

Your app must answer with cart:before_update:result within 5 seconds, echoing the request_id it received. The handshake is fail-open:

  • No answer within 5 seconds — the mutation proceeds.
  • proceed is anything other than false (including omitted) — the mutation proceeds.
  • Several apps opted in — one request is dispatched per opted-in app, in parallel, each with its own request_id. A single proceed: false cancels the operation (cancel wins, regardless of order). Answer exactly once per request you receive.

Event flow

Approved (proceed: true):

  1. cart:before_update — store → app, with { action, item, request_id }.
  2. cart:before_update:result — app → store, within 5 seconds, with { request_id, proceed: true }.
  3. cart:update — store → apps, with the already mutated cart.
  4. cart:add:success or cart:remove:success, according to the operation.

Aborted (proceed: false):

  1. cart:before_update — store → app, with { action, item, request_id }.
  2. cart:before_update:result — app → store, with { request_id, proceed: false, reason? }.
  3. The mutation is aborted. The cart is left untouched, so no cart:update, cart:add:success or cart:remove:success is dispatched. The storefront does not lock the cart, and the add-to-cart button returns from its loading state.
  4. From this point the app owns the outcome. It can show its own message, or run a different operation with cart:add / cart:remove — those go through the normal flow and dispatch the usual cart events.

Example

The app below intercepts every cart mutation and asks the shopper to confirm it, using a UI slot on the product page.

Confirming a cart mutation
import { Column, Button, Text, Row } from "@tiendanube/nube-sdk-jsx";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";
import { theme, styled } from "@tiendanube/nube-sdk-ui";

const Container = styled(Column)`
padding-bottom: 16px;
gap: 8px;
`;

const Title = styled(Text)`
font-size: ${theme.typography.lg.fontSize};
color: ${theme.color.success.medium};
`;

export function App(nube: NubeSDK) {
// Without this flag the storefront never dispatches cart:before_update
nube.send("config:set", () => ({
config: {
handle_cart_before_update: true,
},
}));

nube.on("cart:before_update", (state) => {
const payload = state.eventPayload as
| {
request_id: string;
action: "ADD" | "REMOVE";
item: {
product_id: number | null;
variant_id: number | null;
previous_quantity: number;
new_quantity: number;
};
}
| undefined;

if (!payload?.request_id) return;

const { request_id, action, item } = payload;

const respond = (proceed: boolean, reason?: string) => {
nube.send("cart:before_update:result", () => ({
eventPayload: { request_id, proceed, reason },
}));
nube.clearSlot("before_add_to_cart_pdp");
};

nube.render("before_add_to_cart_pdp", () => (
<Container>
<Title>
{action === "ADD"
? `Add ${item.new_quantity - item.previous_quantity} unit(s)?`
: "Remove this item from the cart?"}
</Title>
<Row gap={2}>
<Button onClick={() => respond(true)}>CONFIRM</Button>
<Button onClick={() => respond(false, "user_cancelled")}>
CANCEL
</Button>
</Row>
</Container>
));
});
}
Testing it
  1. Open any product page with your app running in local mode (see DevTools ).
  2. Add the product to the cart.
  3. The CONFIRM and CANCEL buttons appear in the before_add_to_cart_pdp slot.

Use the Events tab of the DevTools to follow the whole flow and confirm which events are dispatched in each case.

cart:before_update:result

Storefront only. Dispatched by app to answer a cart:before_update request. Requires handle_cart_before_update: true in the script configuration — without it the app never receives a request to answer.

eventPayload

PropertyTypeRequiredDescription
request_idstringYesThe request_id received in cart:before_update. Results without it are ignored.
proceedbooleanYestrue lets the mutation continue; false aborts it. Any value other than false is treated as "proceed".
reasonstringNoShort code explaining the verdict (e.g. "user_cancelled"). Used for diagnostics only, never shown to the shopper.
Example
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
nube.send("config:set", () => ({
config: {
handle_cart_before_update: true,
},
}));

nube.on("cart:before_update", (state) => {
const { request_id, item } = (state.eventPayload ?? {}) as {
request_id?: string;
item?: { new_quantity: number };
};

if (!request_id) return;

// Block carts with more than 10 units of the same item
const proceed = (item?.new_quantity ?? 0) <= 10;

nube.send("cart:before_update:result", () => ({
eventPayload: {
request_id,
proceed,
reason: proceed ? undefined : "max_quantity_exceeded",
},
}));
});
}
note

Results carrying an unknown request_id — or one already resolved, for example after the 5 second window elapsed — are ignored.

cart:view

Storefront only. Dispatched by store when the user views their shopping cart. Use this to run logic when the cart page is displayed.

Example
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
nube.on("cart:view", (state) => {
console.log("User is viewing cart with", state.cart.items.length, "items");
});
}

cart:open

Storefront only. Dispatched by app to open the store's cart drawer (side cart). Use it to bring the shopper to the cart without a page navigation — for example right after adding an item, or from a button rendered in a UI slot.

This event takes no payload, so nube.send needs no second argument:

Example
import { Button } from "@tiendanube/nube-sdk-jsx";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
nube.render("before_main_content", () => (
<Button
onClick={() => {
nube.send("cart:open");
}}
>
OPEN CART
</Button>
));
}

A common pattern is to open the cart as soon as an item is added, so the shopper sees the result of the action:

Opening the cart after adding an item
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
nube.on("cart:add:success", () => {
nube.send("cart:open");
});
}

Behavior

The event opens whatever cart drawer the current theme provides — a modal cart on newer themes, or the legacy ajax cart panel on older ones. It is a no-op (nothing happens, no error) when there is no drawer to open:

  • The theme has no cart drawer, or the ajax cart is disabled in the store.
  • The shopper is on the cart page itself, where the cart is the page content and not a drawer.
  • The drawer is already open.
note

On themes with a modal cart, opening the drawer also dispatches cart:view , so listeners on that event run as well. Sending cart:open does not change the cart content, so it never dispatches cart:update.

Testing it

Run your app in local mode (see DevTools ), open any storefront page and trigger the send. Use the Events tab to confirm that cart:open was dispatched.

cart:validate

Checkout only. Dispatched by app to signal if the content of the cart is valid or not. Requires has_cart_validation: true in the script configuration to work, otherwise cart validation events are ignored.

Example
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
// Tell NubeSDK that this script wants to validate the content of the cart
nube.send("config:set", () => ({
config: {
has_cart_validation: true,
},
}));

nube.on("cart:update", ({ cart }) => {
if (cart.items.length < 5) {
nube.send("cart:validate", () => ({
cart: {
validation: {
status: "fail",
reason: "Cart must have at least 5 items!",
},
},
}));
} else {
nube.send("cart:validate", () => ({
cart: {
validation: {
status: "success",
},
},
}));
}
});
}

cart:add

Dispatched by app to add an item to the cart.

Each item in the items array supports the following fields:

PropertyTypeRequiredDescription
variant_idnumberYesUnique identifier for the product variant to add.
product_idnumberYesUnique identifier for the product.
quantitynumberYesQuantity to add.
propertiesArray<unknown> or Record<string, unknown>NoCustom properties for the item (e.g. personalization, gift messages).
Example
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
nube.send("cart:add", () => ({
cart: {
items: [
{
// Use real variant_id and product_id from your store
// (e.g. from state.location.page.data on a product page)
variant_id: 123,
product_id: 321,
quantity: 1,
properties: {
gift_message: "Happy Birthday!",
},
},
],
},
}));
}

cart:add:success

Dispatched by store when an item is successfully added to the cart.

eventPayload

This event includes state.eventPayload with details about the item(s) that were added. The payload shape depends on the context:

  • Storefront: a single cart item object.
  • Checkout: an array of cart item objects (one per item successfully added).

Each cart item in the payload has the following properties:

PropertyTypeDescription
idnumberUnique identifier for the product instance in the cart.
namestringName of the product.
pricestringPrice in string format.
quantitynumberQuantity added.
free_shippingbooleanWhether the product qualifies for free shipping.
product_idnumberUnique identifier for the product.
variant_idnumberUnique identifier for the selected product variant.
thumbnailstringURL of the product's thumbnail image.
variant_valuesstringVariant details (e.g. selected attributes).
skustring or nullSKU (Stock Keeping Unit) for the product variant.
propertiesArray<unknown> or Record<string, unknown>Additional product properties.
urlstringURL of the product's page.
is_ahora_12_eligiblebooleanWhether the product is eligible for Ahora 12 financing.
Example
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
nube.on("cart:add:success", (state) => {
const payload = state.eventPayload;

// In storefront, payload is a single item object
// In checkout, payload is an array of item objects
const item = Array.isArray(payload) ? payload[0] : payload;

console.log("Item added to cart:", item?.variant_id, item?.name);
});
}

cart:add:fail

Dispatched by store when there's a failure in adding an item to the cart.

note

This event does not include eventPayload. The state.eventPayload will be null.

Example
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
nube.on("cart:add:fail", (state) => {
console.log("Failed to add item to cart");
});
}

cart:remove

Dispatched by app to remove an item from the cart.

Example
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
nube.send("cart:remove", () => ({
cart: {
items: [
{
// Use real variant_id and product_id from the item to remove (e.g. from state.cart.items)
variant_id: 123,
product_id: 321,
quantity: 1,
},
],
},
}));
}

cart:remove:success

Dispatched by store when an item is successfully removed from the cart. The listener receives the updated state; state.cart.items reflects the removal.

eventPayload

This event includes state.eventPayload with details about the item(s) that were removed. The payload shape depends on the context:

  • Storefront: a single cart item object.
  • Checkout: an array of cart item objects (one per item successfully removed).

The cart item shape is the same as cart:add:success .

Example
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
nube.on("cart:remove:success", (state) => {
const payload = state.eventPayload;
const item = Array.isArray(payload) ? payload[0] : payload;

console.log(
"Item removed:", item?.variant_id,
"— cart now has", state.cart.items.length, "items",
);
});
}

cart:remove:fail

Dispatched by store when there's a failure in removing an item from the cart.

note

This event does not include eventPayload. The state.eventPayload will be null.

Example
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
nube.on("cart:remove:fail", (state) => {
console.log("Failed to remove item from cart");
});
}

Help us improve NubeSDK

Found an issue or have a suggestion? Let us know on GitHub.