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.
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 :
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
| Property | Type | Description |
|---|---|---|
request_id | string | Unique 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. |
item | object | The cart line about to change. See the table below. |
The item object:
| Property | Type | Description |
|---|---|---|
product_id | number or null | Identifier of the product. null when the storefront cannot resolve it. |
variant_id | number or null | Identifier of the selected variant. null when the storefront cannot resolve it. |
previous_quantity | number | Quantity of this item in the cart before the operation. |
new_quantity | number | Quantity it would have after the operation. 0 when the item is being removed. |
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.
proceedis anything other thanfalse(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 singleproceed: falsecancels the operation (cancel wins, regardless of order). Answer exactly once per request you receive.
Event flow
Approved (proceed: true):
cart:before_update— store → app, with{ action, item, request_id }.cart:before_update:result— app → store, within 5 seconds, with{ request_id, proceed: true }.cart:update— store → apps, with the already mutated cart.cart:add:successorcart:remove:success, according to the operation.
Aborted (proceed: false):
cart:before_update— store → app, with{ action, item, request_id }.cart:before_update:result— app → store, with{ request_id, proceed: false, reason? }.- The mutation is aborted. The cart is left untouched, so no
cart:update,cart:add:successorcart:remove:successis dispatched. The storefront does not lock the cart, and the add-to-cart button returns from its loading state. - 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.
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>
));
});
}
- Open any product page with your app running in local mode (see DevTools ).
- Add the product to the cart.
- The CONFIRM and CANCEL buttons appear in the
before_add_to_cart_pdpslot.
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
| Property | Type | Required | Description |
|---|---|---|---|
request_id | string | Yes | The request_id received in cart:before_update. Results without it are ignored. |
proceed | boolean | Yes | true lets the mutation continue; false aborts it. Any value other than false is treated as "proceed". |
reason | string | No | Short code explaining the verdict (e.g. "user_cancelled"). Used for diagnostics only, never shown to the shopper. |
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",
},
}));
});
}
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.
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:
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:
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.
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.
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.
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:
| Property | Type | Required | Description |
|---|---|---|---|
variant_id | number | Yes | Unique identifier for the product variant to add. |
product_id | number | Yes | Unique identifier for the product. |
quantity | number | Yes | Quantity to add. |
properties | Array<unknown> or Record<string, unknown> | No | Custom properties for the item (e.g. personalization, gift messages). |
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:
| Property | Type | Description |
|---|---|---|
id | number | Unique identifier for the product instance in the cart. |
name | string | Name of the product. |
price | string | Price in string format. |
quantity | number | Quantity added. |
free_shipping | boolean | Whether the product qualifies for free shipping. |
product_id | number | Unique identifier for the product. |
variant_id | number | Unique identifier for the selected product variant. |
thumbnail | string | URL of the product's thumbnail image. |
variant_values | string | Variant details (e.g. selected attributes). |
sku | string or null | SKU (Stock Keeping Unit) for the product variant. |
properties | Array<unknown> or Record<string, unknown> | Additional product properties. |
url | string | URL of the product's page. |
is_ahora_12_eligible | boolean | Whether the product is eligible for Ahora 12 financing. |
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.
This event does not include eventPayload. The state.eventPayload will be null.
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.
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 .
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.
This event does not include eventPayload. The state.eventPayload will be null.
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.