> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aftersell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom scripts

> Run custom JavaScript in the Aftersell Cart with the Initialization and On cart update script slots.

Custom scripts let you run your own JavaScript against the cart using the [Cart SDK](/aftersell/cart/sdk-overview). Add them in the cart editor under **Cart settings → Custom script**, where a dropdown switches between two slots: **Initialization** and **On cart update**.

Write plain JavaScript in these editors, with no `<script>` tags. **On cart update** has a **Reset to default** action that restores its starter template; **Initialization** does not, so keep your own copy before you clear it.

<Note>
  A lot of what merchants used to script is now a built-in setting. Check [Before you write a script](/aftersell/cart/sdk-use-cases#before-you-write-a-script) first: a setting keeps working through cart redesigns, and your script might not.
</Note>

## Which slot to use

|                | Initialization                                                                                                                                                      | On cart update                                                          |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| **Runs**       | Once, when the cart loads.                                                                                                                                          | On every cart change after the first load.                              |
| **You write**  | The whole script.                                                                                                                                                   | Only the handler body. The `cart_updated` wrapper is locked.            |
| **Use it for** | Registering behavior once: [`configure`](/aftersell/cart/sdk-configure), [`events.on`](/aftersell/cart/sdk-events), [`hooks.register*`](/aftersell/cart/sdk-hooks). | Rules that have to be re-evaluated against the cart's current contents. |
| **Example**    | Hide free gift lines with a line transform.                                                                                                                         | Keep a free gift in sync with a spend threshold.                        |

## Initialization

The **Initialization** script runs **once when the cart loads**. It's your entry point for setting things up: configuring cart behavior, subscribing to events, and registering hooks. The [SDK](/aftersell/cart/sdk-overview) is available as `window.aftersell.cart`.

Setup calls you make here ([`configure(...)`](/aftersell/cart/sdk-configure), [`events.on(...)`](/aftersell/cart/sdk-events), [`hooks.*`](/aftersell/cart/sdk-hooks)) are safe to call at the top of the script even before the cart has fully booted; they're buffered and applied once it does. Actions that read or change the cart (like [`addItem`](/aftersell/cart/sdk-actions#additemvariantid-quantity) or [`getCart`](/aftersell/cart/sdk-actions#getcart)) should run inside [`ready()`](/aftersell/cart/sdk-overview#ready) or an event handler.

The slot starts out with three **commented-out** examples — opening the drawer on every add, reacting to `cart_loaded`, and hiding free gift lines — so an untouched Initialization script does nothing. Uncomment one to try it, or replace them.

The natural shape for this slot is a **one-time registration with no events involved**: register the behavior once and let the cart apply it from then on. Hiding free gift lines from the drawer, without changing the total, is the shipped example of that:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerLineTransform((line) => {
  if (line.finalLinePrice === 0) line.setHidden(true);
});
```

[`registerLineTransform`](/aftersell/cart/sdk-hooks#registerlinetransform) runs for every line as it renders, and `setHidden` is display-only, so the line stays in the cart and still counts toward the total, it just doesn't show in the drawer. See [Hide and relabel cart lines](/aftersell/cart/sdk-use-case-hide-lines) for more of what a transform can do.

Actions that read the cart go inside `ready()`:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.ready().then(() => {
  const state = window.aftersell.cart.actions.getCart();
  console.log('Cart loaded with', state.itemCount, 'items');
});
```

Reaching into the cart's DOM needs the same wait, and it needs [`shadowRoot`](/aftersell/cart/sdk-overview#shadowroot): the cart renders inside a shadow root, so `document.querySelector` can't see anything in the drawer.

<Tip>
  Branching on market, country, or currency **before** the cart loads? Read [`context`](/aftersell/cart/sdk-overview#context) instead. It's available synchronously, with no `ready()` needed, so you can skip registering handlers entirely for shoppers a rule doesn't apply to.
</Tip>

## On cart update

The **On cart update** script runs every time the cart changes. It's a locked wrapper around a `cart_updated` subscription, so you edit only the body, and your code receives the updated `cart`.

This slot is for rules that have to be **re-evaluated on every cart change**. A free gift threshold is the classic case (spend \$75, get a free tote) because the answer depends on the current contents and nothing else can tell you when they change:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('cart_updated', (cart) => {
  const GIFT_VARIANT_ID = 1234567890;
  const THRESHOLD = 7500;   // $75.00, in cents

  let giftLine = null;
  let subtotal = 0;
  (cart.items ?? []).forEach((line) => {
    if (line.variantId === GIFT_VARIANT_ID) giftLine = line;
    else subtotal += line.finalLinePrice;   // the gift itself never counts toward the threshold
  });

  const shouldHaveGift = subtotal >= THRESHOLD;
  const hasGift = Boolean(giftLine);

  // Bail when the cart already matches. This is the part that matters: adding or
  // removing an item fires cart_updated again, so without this check the handler
  // re-enters itself forever.
  if (shouldHaveGift === hasGift) return;

  if (shouldHaveGift) window.aftersell.cart.actions.addItem(GIFT_VARIANT_ID, 1);
  else window.aftersell.cart.actions.removeItem(giftLine.key);
});
```

### Keeping the cart in a desired state

The `if (shouldHaveGift === hasGift) return;` line is what makes this safe, and it generalizes to every script that keeps the cart in a desired state. This slot both reacts to cart changes and causes them, so every `addItem` or `removeItem` re-enters it. Describe the state you want, compare it to the state you have, and return early when they already agree, so the handler converges after one pass instead of looping. See [the two rules](/aftersell/cart/sdk-events#the-two-rules) for the unguarded version to avoid and why the payload is read-only.

On a slower store it's also worth keeping a module-level in-flight flag, so two rapid changes can't both start an add before the first one lands.

<Note>
  `cart_updated` fires only on changes **after** the first load ([event timing](/aftersell/cart/sdk-events#cart_updated)), so a script in this slot won't reconcile a cart that already qualifies when the page loads. For a version that handles both, subscribe to `cart_loaded` and `cart_updated` with the same function from the **Initialization** slot. See [Auto-add a free gift at a threshold](/aftersell/cart/sdk-use-case-free-gift).
</Note>

## When a script breaks

Each slot runs in its own sandbox, so a broken **Initialization** script can't stop **On cart update** from running, and neither can break the cart itself.

Within a slot, though, execution **stops at the first error**. Everything below that line is skipped, which means any `configure`, `events.on`, or `hooks.register*` further down is never registered. That's the usual explanation for "my handler never fires" when the code looks right.

The cart names the failing line in the browser console, and each slot runs under its own filename (`aftersell-cart-init.js` and `aftersell-cart-cart-update.js`), so you can open either from the DevTools Sources panel and set breakpoints. See [Debugging](/aftersell/cart/sdk-overview#debugging) for the exact messages, and for the debug channel that catches hook failures kept off the console.

Because `cart_loaded` [replays to late subscribers](/aftersell/cart/sdk-events#cart_loaded), registration order never matters. The safest structure is to register everything first and do the risky work inside handlers, where a throw is isolated to that handler.

## Where to go next

* **[Cart SDK](/aftersell/cart/sdk-overview)**: custom scripts are how you run SDK code. See the [configure](/aftersell/cart/sdk-configure), [events](/aftersell/cart/sdk-events), [actions](/aftersell/cart/sdk-actions), and [hooks](/aftersell/cart/sdk-hooks) references for the full surface, the [cart object](/aftersell/cart/sdk-cart-object) for the shape of what handlers receive, and the [use cases](/aftersell/cart/sdk-use-cases) for ready-made snippets.
* **[Custom code blocks](/aftersell/cart/custom-code-blocks)**: for adding markup to the cart. Note the Custom code block's HTML mode does **not** run JavaScript; use custom scripts (or the block's React mode) for logic.
