Skip to main content
Custom scripts let you run your own JavaScript against the cart using the Cart SDK. 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.
A lot of what merchants used to script is now a built-in setting. Check Before you write a script first: a setting keeps working through cart redesigns, and your script might not.

Which slot to use

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 is available as window.aftersell.cart. Setup calls you make here (configure(...), events.on(...), 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 or getCart) should run inside 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:
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 for more of what a transform can do. Actions that read the cart go inside ready():
Reaching into the cart’s DOM needs the same wait, and it needs shadowRoot: the cart renders inside a shadow root, so document.querySelector can’t see anything in the drawer.
Branching on market, country, or currency before the cart loads? Read 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.

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:

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 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.
cart_updated fires only on changes after the first load (event timing), 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.

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 for the exact messages, and for the debug channel that catches hook failures kept off the console. Because cart_loaded replays to late subscribers, 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: custom scripts are how you run SDK code. See the configure, events, actions, and hooks references for the full surface, the cart object for the shape of what handlers receive, and the use cases for ready-made snippets.
  • 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.