# Basket (Cart)

The **Limio basket** (aka cart) holds the current order state. Use it to add offers, add add-ons, apply promo codes, and checkout.

Most logic lives in the `useBasket()` hook. It exposes state plus mutation methods.

{% hint style="info" %}
**Rule of thumb:** call `initiateCheckout()` for the first item.\
Call `addOfferToBasket()` for subsequent items.
{% endhint %}

### Quick flow (offer → basket → checkout)

```tsx
import React from "react"
import { useBasket } from "@limio/sdk"
import { getCurrentBasketId } from "@limio/shop/src/shop/checkout/basket"

export const SelectOfferButton = ({ offer }) => {
  const {
    basketLoading,
    initiateCheckout,
    addOfferToBasket,
    navigateToCheckout,
    pageOptions,
  } = useBasket()

  const onSelect = async () => {
    const checkoutId = getCurrentBasketId()

    if (!checkoutId) {
      await initiateCheckout({ order: { orderItems: [{ offer, quantity: 1 }] } })
    } else {
      await addOfferToBasket({ offer, quantity: 1 })
    }

    if (pageOptions?.pushToCheckout) {
      await navigateToCheckout()
    }
  }

  return (
    <button disabled={basketLoading} onClick={onSelect}>
      {basketLoading ? "Working…" : offer?.data?.attributes?.cta_text__limio || "Select"}
    </button>
  )
}
```

### `useBasket()`

`useBasket()` returns basket state and methods:

```typescript
const {
  // State
  orderItems,        // Current items in the basket
  basketLoading,     // True when an async basket operation is in progress
  formattedTotal,    // Formatted total price string (e.g., "$29.00")
  expiresAt,         // UTC ISO timestamp of basket expiration
  pageOptions,       // Page configuration options

  // Methods
  initiateCheckout,
  addOfferToBasket,
  removeFromBasket,
  updateItemQuantity,
  swapOffer,
  clearOrderItems,
  navigateToCheckout,
  redeemPromoCode,
  removePromoCode,
  updateCustomField,
  setCheckoutDisabled,
  validateBasket,
  updateBasketDetails,

  // Subscription update checkouts (see dedicated page)
  selectOfferForSubscriptionUpdate,
  addSubscriptionOrderItem,

  // Legacy (deprecated)
  addToBasket,       // Use initiateCheckout or addOfferToBasket instead
  goToCheckout,      // Use navigateToCheckout instead
  basketItems,       // Use orderItems instead
} = useBasket()
```

{% hint style="warning" %}
Only one async basket operation can run at a time. Starting a second operation will throw. Use `basketLoading` to guard UI.
{% endhint %}

### State

#### `basketLoading`

`basketLoading` is `true` while an async basket mutation runs. Disable buttons to prevent double submits.

```tsx
import React from "react"
import { useBasket } from "@limio/sdk"

const AddToBasketButton = ({ offer }) => {
  const { initiateCheckout, basketLoading } = useBasket()

  return (
    <button
      disabled={basketLoading}
      onClick={() => initiateCheckout({ order: { orderItems: [{ offer, quantity: 1 }] } })}
    >
      {basketLoading ? "Adding..." : "Start Free Trial"}
    </button>
  )
}
```

#### `orderItems`

The list of items currently in the basket. Use it to render a mini cart, cart page, or order summary.

{% hint style="info" %}
`orderItems` was called `basketItems` before release 109. The shape is the same. See [Legacy (deprecated)](#legacy-deprecated) below.
{% endhint %}

```javascript
import React from "react"
import { useBasket } from "@limio/sdk"

const Basket = () => {
  const { orderItems } = useBasket()

  return (
    <section>
      <h1>Your Basket</h1>
      {orderItems.map((item) => {
        const attrs = item?.offer?.data?.attributes || {}
        return (
          <div key={item.id}>
            <h2>{item.offer?.name}</h2>
            <p>{attrs.display_description__limio}</p>
            <p>{attrs.display_price__limio}</p>
          </div>
        )
      })}
    </section>
  )
}
```

<details>

<summary>Example <code>orderItems</code> payload</summary>

{% code fullWidth="false" %}

```javascript
[
  {
    id: "4b35bb3e-a50e-4eb2-9946-ebff2876f987",
    quantity: 1,
    offer: {
      id: "d5b8eca9273dba0b971c33d56c3f81c65b6aaa87.97d170e1550eee4afc0af065b78cda302a97674c",
      name: "Leemeeo Premium Monthly",
      path: "/offers/Leemeeo Plans/Leemeeo Premium Monthly",
      parent_path: "/offers/Leemeeo Plans",
      type: "item",
      data: {
        name: "Leemeeo Premium Monthly",
        tags: ["/tags/premium"],
        segments: [],
        attributes: {
          // All configured offer attributes will be returned
          price__limio: [
            {
              delay_interval_type: "months",
              subscription_start_day: "",
              name: "charge_1",
              repeat_interval: 1,
              attributes: [],
              label: "Charge 1",
              trigger: "order_date",
              repeat_interval_type: "months",
              repeat_count: 1,
              type: "recurring",
              value: "29.00",
              currencyCode: "USD"
            }
          ],
          display_description__limio: "Full access to all Leemeeo Premium features",
          display_price__limio: "$29/mo",
          detailed_display_price__limio: "$29.00/mo, billed monthly. Cancel anytime.",
          offer_features__limio:
            "<h3>Leemeeo Premium includes:</h3><ul><li>Unlimited projects</li><li>Team collaboration (up to 10 seats)</li><li>Priority support</li><li>Advanced analytics dashboard</li><li>Custom integrations via API</li><li>SSO and role-based access</li></ul>",
          cta_text__limio: "Start Free Trial",
          payment_types__limio: ["zuora_card"],
          display_name__limio: "Leemeeo Premium Monthly",
          group__limio: "Leemeeo",
          checkout__limio: {
            checkout_type: "standard"
          },
          autoRenew__limio: true
        },
        attachments: [
          {
            path: "/assets/Images/LeemeeoLogo",
            name: "LeemeeoLogo",
            type: "image/png",
            url: "/public/f60388d3-28ca-4a5c-ba38-51cf04eb93bd/leemeeo-logo.png"
          }
        ],
        products: [
          {
            name: "Leemeeo Premium",
            path: "/products/Leemeeo Premium",
            attributes: {
              product_code__limio: "LEEMEEO-PREM-001"
            },
            type: "item"
          }
        ],
        type: "item",
        familyName: "offers"
      }
    },
    upsell: [
      {
        mode: "production",
        path: "/offers/Leemeeo Plans/Leemeeo Business Annual",
        data: {
          name: "Leemeeo Business",
          attributes: {
            display_name__limio: "Leemeeo Business Annual"
          }
        }
      }
    ],
    crossSell: [
      {
        mode: "production",
        path: "/offers/Leemeeo Add-ons/Leemeeo Analytics Add-on",
        data: {
          name: "Leemeeo Analytics Add-on",
          attributes: {
            display_name__limio: "Leemeeo Analytics Add-on"
          }
        }
      }
    ]
  }
]

```

{% endcode %}

</details>

#### `formattedTotal`

`formattedTotal` is a pre-formatted total string (e.g. `"$29.00"`). Use it for displaying the basket total in the UI without manual formatting.

#### `expiresAt`

UTC ISO timestamp of basket expiration. Baskets expire based on the original session — by default, two weeks after creation. Use it to block checkout on expired sessions.

```tsx
import React from "react"
import { useBasket } from "@limio/sdk"

export const CheckoutButton = () => {
  const { expiresAt, basketLoading, navigateToCheckout } = useBasket()

  const isExpired =
    Boolean(expiresAt) && new Date(expiresAt).getTime() < Date.now()

  return (
    <button disabled={basketLoading || isExpired} onClick={() => navigateToCheckout()}>
      {isExpired ? "Basket expired" : "Checkout"}
    </button>
  )
}
```

### Create or update a basket

#### `initiateCheckout` (new basket)

Use `initiateCheckout` when starting a fresh basket (no existing checkoutId). This is the entry point on pricing pages and offer cards — it creates a new basket on the server and returns the checkout ID.

```typescript
await initiateCheckout(options: InitiateCheckoutOptions): Promise<InitiateOrderResponse>
```

**Options:**

You can pass either a single order item or an order object with multiple items:

```typescript
// Single item (legacy format, still supported)
{ offer: ElasticOffer, quantity?: number, type?: string }

// Multiple items (recommended)
{ order: { orderItems: [{ offer, quantity, type }], order_type?: string } }
```

**Example — basic usage:**

```tsx
import React from "react"
import { useBasket } from "@limio/sdk"
import { getCurrentBasketId } from "@limio/shop/src/shop/checkout/basket"

const OfferCard = ({ offer }) => {
  const { basketLoading, initiateCheckout, addOfferToBasket, navigateToCheckout, pageOptions } = useBasket()

  async function selectOffer() {
    const checkoutId = getCurrentBasketId()

    // Use initiateCheckout for new baskets, addOfferToBasket for existing ones
    if (!checkoutId) {
      await initiateCheckout({ order: { orderItems: [{ offer }] } })
    } else {
      await addOfferToBasket({ offer })
    }

    // Optionally navigate to checkout if configured
    if (pageOptions.pushToCheckout) {
      await navigateToCheckout()
    }
  }

  return (
    <button onClick={selectOffer} disabled={basketLoading}>
      {offer.data.attributes.cta_text__limio || "Subscribe"}
    </button>
  )
}
```

**Example — multiple items (main offer + add-on):**

```tsx
const { initiateCheckout } = useBasket()

const handlePurchase = async (mainOffer, addonOffer) => {
  const { id, order } = await initiateCheckout({
    order: {
      orderItems: [
        { offer: mainOffer, quantity: 1 },
        { offer: addonOffer, quantity: 1, parentId: mainOffer.id }
      ]
    }
  })
}
```

**Note:** This method automatically:

* Creates a new basket on the server
* Sends `add_to_cart` analytics events
* Sets tracking tags for the offer

***

#### `addOfferToBasket` (existing basket)

Use `addOfferToBasket` to add items to an existing basket (checkoutId already exists). Common for add-on selection and multi-item carts.

```typescript
await addOfferToBasket(orderItem: InitialOrderItem): Promise<{ id: string }>
```

**Example — adding an add-on linked to a parent offer:**

```tsx
import React from "react"
import { useBasket } from "@limio/sdk"

const AddAddonButton = ({ addon, parentItemId }) => {
  const { addOfferToBasket, basketLoading } = useBasket()

  const handleAddAddon = async () => {
    await addOfferToBasket({
      offer: addon,
      quantity: 1,
      type: "addon",
      parentId: parentItemId
    })
  }

  return (
    <button disabled={basketLoading} onClick={handleAddAddon}>
      Add {addon.data.attributes.display_name__limio}
    </button>
  )
}
```

***

### Update or remove items

#### `removeFromBasket`

Remove an item from the basket by its ID. Used in cart item components and basket popovers to let users remove selections.

```typescript
await removeFromBasket(item: { id: string }): Promise<void>
```

**Example — cart item with a remove button:**

```tsx
import type { OrderItem } from "@limio/types"
import React from "react"
import { useBasket } from "@limio/sdk"
import { Button, Icon, Text } from "@limio/component-library"
import { faTimes } from "@fortawesome/free-solid-svg-icons"

type CartItemProps = {
  basketItem: OrderItem
}

const CartItem = ({ basketItem }: CartItemProps): React.JSX.Element => {
  const offerAttributes = basketItem.offer.data.attributes
  const product = basketItem.offer.data.products[0]
  const { removeFromBasket } = useBasket()

  const onRemove = async () => {
    await removeFromBasket({ id: basketItem.id })
  }

  return (
    <div>
      <Text size="medium" weight="emphasised">
        {product.attributes.display_name__limio} - {offerAttributes.display_name__limio}
      </Text>
      <Button size="icon" variant="text" onClick={onRemove}>
        <Icon icon={faTimes} size="lg" />
      </Button>
    </div>
  )
}

export default CartItem
```

#### `swapOffer`

Atomically replace one offer with another. Used for in-cart upsell and downgrade buttons — it removes the old item and adds the new offer in a single operation.

```typescript
await swapOffer(itemId: string, offer: ElasticOffer): Promise<void>
```

**Example — upsell button inside a cart item:**

```tsx
import React from "react"
import { useBasket } from "@limio/sdk"

const UpsellButton = ({ currentItemId, upgradeOffer }) => {
  const { swapOffer, basketLoading } = useBasket()

  return (
    <button
      disabled={basketLoading}
      onClick={() => swapOffer(currentItemId, upgradeOffer)}
    >
      Upgrade to {upgradeOffer.data.attributes.display_name__limio}
    </button>
  )
}
```

**Note:** This method sends both `remove_from_cart` and `add_to_cart` analytics events and syncs the basket with the server.

#### `updateItemQuantity`

Update the quantity of an existing item. Used in quantity selectors on cart pages and floating cart dialogs.

```typescript
await updateItemQuantity(itemId: string, quantity: number): Promise<void>
```

**Example — quantity control:**

```tsx
import React from "react"
import { useBasket } from "@limio/sdk"

const QuantitySelector = ({ item }) => {
  const { updateItemQuantity, basketLoading } = useBasket()

  return (
    <div>
      <button
        disabled={basketLoading || item.quantity <= 1}
        onClick={() => updateItemQuantity(item.id, item.quantity - 1)}
      >
        -
      </button>
      <span>{item.quantity}</span>
      <button
        disabled={basketLoading}
        onClick={() => updateItemQuantity(item.id, item.quantity + 1)}
      >
        +
      </button>
    </div>
  )
}
```

#### `clearOrderItems`

Remove all items from the basket. Used to reset state before significant changes, such as blocking a downgrade or starting a new selection flow.

```typescript
await clearOrderItems(): Promise<void>
```

***

### Promotions

#### `redeemPromoCode`

Validate and apply a promo code to the basket. Returns the checkout ID on success.

```typescript
await redeemPromoCode(promoCode: string): Promise<{ id: string }>
```

**Example — promo code input with error handling:**

```tsx
import React, { useState } from "react"
import { useBasket } from "@limio/sdk"

const PromoCodeRedeem = () => {
  const { redeemPromoCode } = useBasket()
  const [promoCode, setPromoCode] = useState("")
  const [error, setError] = useState("")

  const onSubmit = async () => {
    try {
      const { id } = await redeemPromoCode(promoCode)
      if (id) setPromoCode("")
    } catch (err) {
      setError("Invalid promo code")
    }
  }

  return (
    <div>
      <input
        value={promoCode}
        onChange={(e) => setPromoCode(e.target.value)}
        placeholder="Enter promo code (e.g. LEEMEEO20)"
      />
      <button onClick={onSubmit}>Apply</button>
      {error && <p>{error}</p>}
    </div>
  )
}
```

#### `removePromoCode`

Remove a previously applied promo code from the basket. The customer can then apply a different code or proceed without a discount.

```typescript
await removePromoCode(promoCode: string): Promise<{ id: string }>
```

**Example:**

```tsx
import React from "react"
import { useBasket } from "@limio/sdk"

const PromoCodeRemove = ({ appliedCode }) => {
  const { removePromoCode } = useBasket()

  return (
    <button onClick={() => removePromoCode(appliedCode)}>
      Remove promo code
    </button>
  )
}
```

***

### Checkout

#### `navigateToCheckout`

Redirect to the checkout page. Typically called after adding items to the basket, often controlled by `pageOptions.pushToCheckout`.

```typescript
await navigateToCheckout(options?: GoToCheckoutOptions): Promise<void>
```

**Options:**

```typescript
type GoToCheckoutOptions = {
  journey?: JourneyFunnel  // Optional journey configuration with checkout settings
}
```

**Example:**

```tsx
import React from "react"
import { useBasket } from "@limio/sdk"

const CheckoutButton = () => {
  const { navigateToCheckout, orderItems, basketLoading } = useBasket()

  return (
    <button
      onClick={() => navigateToCheckout()}
      disabled={basketLoading || orderItems.length === 0}
    >
      Proceed to Checkout
    </button>
  )
}
```

**Note:** This method requires a valid basket ID to exist. If no basket has been initiated, it will throw an error.

***

#### `setCheckoutDisabled`

Gate checkout on a condition like terms acceptance. The checkout button remains disabled until `setCheckoutDisabled(false)` is called.

```typescript
setCheckoutDisabled(value: boolean): void
```

**Example — terms acceptance gate:**

```tsx
import React from "react"
import { useBasket } from "@limio/sdk"

export const TermsGate = () => {
  const { setCheckoutDisabled } = useBasket()

  return (
    <label>
      <input
        type="checkbox"
        onChange={(e) => setCheckoutDisabled(!e.target.checked)}
      />
      I accept the terms and conditions
    </label>
  )
}
```

***

### Validation and metadata

#### `validateBasket`

Validate the basket against rules before proceeding to checkout. Currently supports `"preventMixedRates"` to block orders with mixed billing terms.

```typescript
validateBasket(validationType: string): boolean
```

**Example — prevent mixed billing terms at checkout:**

```tsx
import React from "react"
import { useBasket } from "@limio/sdk"

const CheckoutButton = () => {
  const { validateBasket, navigateToCheckout, orderItems } = useBasket()

  const handleCheckout = async () => {
    const isValid = validateBasket("preventMixedRates")
    if (!isValid) {
      alert("Cannot mix different billing terms in the same order")
      return
    }
    await navigateToCheckout()
  }

  return (
    <button onClick={handleCheckout} disabled={orderItems.length === 0}>
      Checkout
    </button>
  )
}
```

***

#### `updateCustomField`

Set custom metadata on the order payload, passed downstream to billing systems like Zuora. Used in checkout custom field components and partner-specific integrations.

```typescript
updateCustomField(customField: string, value: string): void
```

**Example — collecting a company ID during checkout:**

```tsx
import React from "react"
import { useBasket } from "@limio/sdk"

export const CompanyIdField = () => {
  const { updateCustomField } = useBasket()

  return (
    <input
      placeholder="Company ID"
      onChange={(e) => updateCustomField("companyId", e.target.value)}
    />
  )
}
```

If you use Zuora, Limio maps custom fields to the Subscription object by default. Target other objects by suffixing the key:

```javascript
// Maps to the Zuora Account object instead of Subscription
updateCustomField("externalId.account", "ACC-12345")
```

Custom fields appear in the order payload under `customFields`:

```javascript
{
  order: {
    customerDetails: { ... },
    billingDetails: { ... },
    orderItems: [ ... ],
    customFields: {
      "companyId": "ACME-001",
      "externalId.account": "ACC-12345"
    }
  }
}
```

***

#### `updateBasketDetails`

Update basket metadata such as billing address, customer details, or custom fields. Runs as a parallel operation — it won't block other basket mutations.

```typescript
await updateBasketDetails(body: UpdateBasketDetailsBody): Promise<void>
```

**Example — pre-filling customer and billing details:**

```tsx
const { updateBasketDetails } = useBasket()

await updateBasketDetails({
  data: {
    order: {
      customerDetails: {
        firstName: "Jane",
        lastName: "Smith",
        email: "jane@leemeeo.com",
        companyName: "Leemeeo Inc."
      },
      billingDetails: {
        address1: "123 Main St",
        city: "San Francisco",
        state: "CA",
        zipCode: "94102",
        country: "US"
      }
    }
  }
})
```

***

### Subscription update checkouts

For subscription update (change plan) flows, see the dedicated [Subscription Update Checkout](/developers/limio-sdk/subscription-update-checkout.md) page. It covers starting an update checkout with `initiateCheckout` using `order_type: "update_subscription"`, selecting offers with `selectOfferForSubscriptionUpdate`, adding items with `addSubscriptionOrderItem`, and how `updateItemQuantity` behaves differently in update checkouts.

***

### Cart persistence and authentication

For the basket to persist across multiple pages, **anonymous authentication (light auth) or full authentication must be enabled on each page** in the Page Builder. Without this, navigating between pages clears the basket.

{% hint style="warning" %}
If authentication is set to **None** on a page, the basket state will not persist when navigating away. Set authentication to **Anonymous Auth** (light auth) on every page that should share a basket.
{% endhint %}

After changing authentication settings on a page, you must **rebuild and republish** the page. Use an incognito window or hard refresh to verify the change has taken effect. The basket is stored server-side and keyed to the session.

***

### Notes for production UI

* Guard every mutation with `basketLoading`.
* Use `parentId` when adding add-ons linked to a main offer.
* Use `validateBasket("preventMixedRates")` before checkout if your catalog has offers with different billing terms.

***

### Legacy (deprecated)

{% hint style="warning" %}
The methods below are deprecated. They still work for backward compatibility, but new components should use the modern replacements listed for each.
{% endhint %}

#### `addToBasket`

**Replaced by:** `initiateCheckout` (new baskets) or `addOfferToBasket` (existing baskets).

`addToBasket` creates or updates the basket in a single call. It does not give you control over whether the basket is new or existing — the newer APIs split this into two explicit steps.

```typescript
await addToBasket(offer: ElasticOffer, options?: AddToBasketOptions): Promise<void>
```

**Options:**

| Field                 | Type                            | Description                                   |
| --------------------- | ------------------------------- | --------------------------------------------- |
| `quantity`            | `number`                        | Item quantity (default: `1`)                  |
| `parentId`            | `string`                        | Links an add-on to a parent offer             |
| `orderItemActionType` | `"add" \| "remove" \| "update"` | Action type for subscription update checkouts |

**Example — basic usage:**

```javascript
import React from "react"
import { useCampaign, useBasket } from "@limio/sdk"

const Offers = () => {
  const { offers } = useCampaign()
  const { addToBasket } = useBasket()

  return (
    <section>
      {offers.map(offer => (
        <div key={offer.id}>
          <h1>{offer.data.attributes.display_name__limio}</h1>
          <button onClick={() => addToBasket(offer, { quantity: 1 })}>
            Add to basket
          </button>
        </div>
      ))}
    </section>
  )
}
```

**Example — adding an add-on linked to a parent:**

```javascript
addToBasket(addonOffer, { quantity: 1, parentId: mainOffer.id })
```

***

#### `goToCheckout`

**Replaced by:** `navigateToCheckout`.

`goToCheckout` optionally initiates or updates the basket before navigating. The newer `navigateToCheckout` only handles navigation — basket creation is handled separately via `initiateCheckout`.

```typescript
await goToCheckout(basketId?: string, options?: GoToCheckoutOptions): Promise<void>
```

**Example — with journey configuration:**

```javascript
const { goToCheckout } = useBasket()

await goToCheckout(null, {
  journey: {
    checkout: {
      slug: "/checkout",
      checkoutType: "standard"
    }
  }
})
```

***

#### `basketItems`

**Replaced by:** `orderItems`.

`basketItems` is a legacy alias for `orderItems`, kept for backward compatibility. The data shape is identical.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.limio.com/developers/limio-sdk/basket.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
