Cart data

The cart object with its items, options, totals, coupon and free-shipping bar, and how to read and change it.

Use this page when you build a mini-cart, a cart page or a checkout banner and need to know what the cart contains and what each field means.

Read it

The cart lives in the browser: the cart id is stored there, so the server cannot load it. Ask the Salla SDK for the id, then fetch the cart with the cart API module.

app/components/cart/MiniCart.tsx
import { useEffect, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { cart } from '@salla.sa/twilight-theme-engine/api/cart';
import { useMoney } from '@salla.sa/twilight-theme-engine/hooks/useMoney';

export function MiniCart() {
  const { format } = useMoney();
  const [cartId, setCartId] = useState<number | null>(null);

  useEffect(() => {
    window.salla?.cart.api.getCurrentCartId().then(setCartId);
  }, []);

  const { data } = useQuery({ ...cart.queries.detail(cartId!), enabled: !!cartId });

  if (!data) return null; // no cart yet, or still loading
  return (
    <p>
      {data.items.length} items, {format(data.total)}
    </p>
  );
}

The built-in cart page does exactly this and shows a skeleton until the cart arrives. To follow changes live, listen to cart events (React to cart events) and invalidate the query.

The Cart object

Example (illustrative values):

Cart object (illustrative values)
{ 17 }
:"1234567"
:2number of items in the cart
:[ 0 ]
:250the "items total" row of the summary
:265what the customer pays
:0
:25
:"SAVE10"null when no coupon is applied
:15
:true
:truea shipping cost applies
:25
:{ 4 }
:300
:false
:83
:50
:[ 0 ]cart-level options, see CartOption
:null
:{ 3 }
:true
:"Send as a gift"
:"products"
:{ 1 }
:{ 2 }
Click a key to copy its path.

The tables are generated from Cart in @salla.sa/twilight-theme-engine/types. Nested objects get their own tables: items (the lines), their options, offers and attachments, and free_shipping_bar. Example values are in the explorer above.

FieldTypeDescription
idstringCart id. Twig: string — matches API string cart ID (SDK CartSummary incorrectly types this as number)
countnumberNumber of items. Present in SDK CartSummary; not documented in Twig docblock
itemsCartItem[]The cart lines. An empty array means an empty cart.
sub_totalnumberItems total, the first row of the order summary.
totalstring | numberFinal amount.
discountnumberDiscount amount.
coupon?string | nullApplied coupon code, or null.
tax_amountnumberTax amount.
is_require_shipping?booleanWhether anything in the cart needs shipping.
has_shippingbooleanWhether a shipping cost applies now.
real_shipping_costnumberThe shipping cost.
free_shipping_bar?FreeShippingBar | nullProgress towards free shipping. Null or missing when the store does not offer it.
optionsCartOption[]Order-level options.
options_total?number | nullTotal price of the order-level options.
total_discount?number | nullDiscount row of the order summary. Often 0, null or missing: test with !!.
should_refresh?booleanReact-only runtime flag — triggers a client-side cart re-fetch after mutations
gift?{ enabled: boolean; text?: string; type?: string; } | nullGifting settings, or null. Twig exposes gift as a top-level page variable; the REST API sends it inside the cart object.
loyalty?{ prize: { points: number; title: string; }; } | nullThe loyalty reward the customer can win, or null. Twig exposes loyalty as a top-level page variable, not nested inside cart; the REST API returns it inside the cart object, so the loader maps it here.

items

FieldTypeDescription
idstringLine id. Use it to update or remove the line, not product_id. Twig docblock says int; API returns numeric string IDs — kept as string for safe attribute binding
product_idnumberId of the product.
product_namestringProduct name.
product_imagestringImage URL (a plain string, not an image object).
urlstringProduct page URL.
quantitynumberQuantity in the cart.
max_quantity?numberMost the customer can buy.
typestringProduct type, such as "product" or "donating".
pricenumber | MoneyUnit price the customer pays.
product_pricenumber | MoneyUnit price of the product.
original_price?number | MoneyPrice before a sale.
totalnumber | MoneyLine total.
total_special_price?number | MoneyLine total after tiered offers.
weight_label?stringReadable weight, such as "2 kg".
notes?stringThe customer's note.
is_availablebooleanFalse when the product is out of stock.
is_hidden_quantitybooleanWhen true, show the quantity as text instead of a stepper.
is_on_sale?booleanA sale price applies.
has_discount?booleanA discount applies.
can_add_notebooleanThe product accepts a note.
can_upload_filebooleanThe product accepts file attachments.
offer?CartItemOffer | nullA simple offer on this line, or null.
detailed_offers?JsonObject[] | nullDetailed offers, shown by the offers component.
donation?JsonObject | nullDonation data when type is "donating".
attachments?CartItemAttachment[]Files the customer uploaded.
options?CartItemOption[]The options the customer picked.
has_pre_order_campaign?booleanItem has an active pre-order campaign. TODO: add to API response

items.offer

FieldTypeDescription
discountnumberDiscount amount.
is_freebooleanThe item is free.
names?stringOffer name to show.

items.attachments

FieldTypeDescription
idnumberAttachment id.
urlstringFile URL.
product_idnumberProduct the file belongs to.
item_idnumberCart line the file belongs to.
namestringFile name. Twig docblock incorrectly types this as int — it is a filename string

items.options

FieldTypeDescription
idnumberOption id.
name?stringOption name.
value?stringChosen value.
quantity?numberQuantity, when the option has one.
options?Record<string, string | number | boolean>[]Nested option details.

free_shipping_bar

FieldTypeDescription
minimum_amountnumberCart value needed for free shipping.
has_free_shippingbooleanTrue once the customer has reached it.
percentnumberProgress from 0 to 100, ready for a bar width.
remainingnumberAmount still missing. It is a plain number: format it yourself.

options

Same fields as items.options.

gift

FieldTypeDescription
enabledbooleanGifting is on.
text?stringGift message text.
type?stringGift type.

loyalty

FieldTypeDescription
prize{ points: number; title: string; }The reward.

loyalty.prize

FieldTypeDescription
pointsnumberPoints the reward costs.
titlestringReward title.

All amounts are in the store's currency. Format them with useMoney, see Money.

Use a line's id to update or remove it, not product_id. CartItemOption belongs to a line (a size, a colour, a text field); CartOption is an order-level option on cart.options, with the same shape. loyalty and gift are top-level in the API response; the engine copies them onto the cart object.

Common tasks

Show the free-shipping progress:

const bar = cart.free_shipping_bar;
if (bar && !bar.has_free_shipping) {
  return <progress max={100} value={bar.percent} />;
}

Read a price that may be a number or a Money object:

import type { Money } from '@salla.sa/twilight-theme-engine/types';

const amount = (value: number | Money) => (typeof value === 'number' ? value : value.amount);

Change the cart with the Salla SDK, then let the query refresh:

await window.salla?.cart.addItem(productId, 1);
await window.salla?.cart.updateItem({ id: item.id, quantity: 3 });
await window.salla?.cart.deleteItem(item.id);

Apply or remove a coupon with useCoupon:

import { useCoupon } from '@salla.sa/twilight-theme-engine/hooks/useCoupon';

const { value, apply, remove, error, loading } = useCoupon();
await apply('SAVE10'); // resolves true on success, false on failure; `error` holds the message
// `value` starts as null on every page load: read the applied code from `cart.coupon`

Share the cart with child components

CartContextProvider and useCartContext carry a cart down your component tree so you do not pass it through every level.

app/components/cart/CartPage.tsx
import { CartContextProvider, useCartContext } from '@salla.sa/twilight-theme-engine/contexts';

function Count() {
  const ctx = useCartContext(); // null outside a provider
  return <span>{ctx?.cart.count ?? 0}</span>;
}

// <CartContextProvider value={{ cart }}><Count /></CartContextProvider>

The engine's own cart page does not mount this provider, and hook slots do not read it. Mount it yourself in your own cart page, and pass the cart to a slot with context={{ cart }}.

Gotchas

  • price, product_price, total and the other line amounts can be numbers or Money objects. Do not assume one.
  • Cart id is a string in the object, but the API call takes a number (cart.queries.detail(cartId)).
  • After an SDK change, the cached cart is stale until you invalidate it. See the cart API module.
  • loyalty is top-level in the API response and is placed on the cart object for you. gift already arrives inside the cart.
  • free_shipping_bar, coupon, total_discount and the item offer are often absent. Guard each one.

Next

On this page