Skip to main content
Where events let you react to the cart and actions let you change it, hooks change how the cart itself behaves: how lines render, what data they carry, and what happens on add-to-cart. Hooks live under window.aftersell.cart.hooks.
A hook changes what the shopper sees; an action changes what’s in their cart. Hiding a free gift line with a transform leaves it in the cart and in the total. Removing it with removeItem takes it out for real.
Hooks are set-up calls, so they’re safe to register at the very top of your script, with no need to wait for ready(). Register them in your cart’s Initialization script (see Custom scripts).

How registration works

Every hook is a register* method. You call it with your function; it returns an unregister function you can call to remove yours.
Registration is additive, so your function runs alongside every other one. That matters because your script is rarely the only one on the page: a subscription app, a bundle app, and the theme itself may all register against the same hook. None of them can replace yours, and nothing you register can be silently dropped by whatever loads after you. A hook that throws, or that isn’t a function, is skipped; the rest still run, and the cart carries on. One broken integration can’t take down add-to-cart, the subscription picker, or the sort. The flip side is that a broken hook of yours fails silently: nothing reaches the browser console. See Debugging for where those failures do surface.

registerLineTransform

registerLineTransform(fn) runs for every cart line before it renders. Use it to hide a line or change how it reads, without touching what’s actually in the shopper’s cart. The function receives a read-only line plus setters. It returns an unregister function.
A transform only changes what renders. It can’t change price, quantity, or line identity. Use the actions for that.
Use it for: hiding gift-with-purchase or app-injected lines, relabelling subscription lines, tagging discounted items, hiding bundle components the shopper shouldn’t manage individually. setInternalProperties is the setter behind bundle grouping: stamping the canonical bundle properties onto each line is how you make a third-party app’s separate cart lines render as one item. See Group bundle lines from another app.

registerLineComparator

A comparator in the same shape Array.prototype.sort expects. It runs after hide and rename, so it sees the transformed lines.
Comparators compose as tie-breakers: the first one to return a non-zero value decides that pair, and the rest are consulted only on ties. Return 0 for pairs you have no opinion about. That’s what hands the decision to the next comparator instead of forcing an order on it. Use it for: floating subscriptions or high-value items to the top, sinking free gifts and add-ons to the bottom, keeping a sponsored product first.

registerCartEnricher

registerCartEnricher(registration) fetches extra product or variant data from the Shopify Storefront API and attaches it to each matching cart line at line.metadata[id]. Use it to surface metafields, tags, or anything else the Storefront API exposes, with no code change from Aftersell required. Returns an unregister function. Whenever the cart loads or changes, Aftersell fetches your fragment for every product or variant on the cart and attaches the result. The fetch is non-blocking: the cart renders immediately and re-emits cart_updated once the data lands. A slow or failing fragment never delays or breaks the cart.
Because enrichment is asynchronous, always guard the read, since line.metadata.pricing is undefined until the first fetch resolves, and metadata itself defaults to {}. Use it for: pulling a metafield onto every line (a delivery estimate, an ingredient list, a “ships separately” flag, a loyalty multiplier) and rendering it through a Custom code block. See showing metafield data on cart lines.
Multiple enrichers coexist happily, since each id is its own namespace, so their data never collides.
Enriched values are returned as-is from the Storefront API and are not sanitized. Render them as text, not as raw HTML.

registerSubscriptionOptionsTransform

Hide or rename the selling plans offered on a line. Your function receives read-only options plus setters, and returns nothing.
Setters, not a returned list, so that several scripts can coexist. If this hook returned an array, a transform that only cared about one plan would naturally write options.filter(...) and silently delete every other app’s plans on its way out. With setters you can only describe your own edits: patches merge per plan and per field, and the last writer wins a genuine conflict on the same field of the same plan. A transform that throws contributes nothing, and the others still apply. Every transform sees the original options, not a half-patched view, so registration order doesn’t change what you’re reading.
Plan order stays as Shopify returned it, so a transform can’t reorder. To control which plan is offered first (and which one the one-time upgrade button subscribes to), use registerDefaultSubscriptionOptionSelector, which promotes its pick to the front.
You also can’t add a plan or change a price: discountPercent has no setter, because a plan Shopify won’t honor at checkout would just be a broken promise in the picker.

registerDefaultSubscriptionOptionSelector

Choose which plan is preselected on a line. Return a plan id, or null to pass on it.
The first selector to return the id of an available plan wins, so return null for the lines you don’t care about rather than guessing. That hands the decision to the next selector instead of overriding it. An id that doesn’t match any plan on the line is treated the same as null and defers too, so a stale id can’t blank out the picker. Your function receives (options, context), the same context the options transform gets.

registerSkipAddToCartRule

Return true to let a specific product form add to the cart normally, bypassing Aftersell entirely. This is useful for a form that needs its own redirect or handling.
Any true skips, so keep your rule narrow, matching the specific forms you own, and return false for everything else. Rules are evaluated in registration order and stop at the first true, so don’t put side effects in one: whether yours runs at all depends on what registered before it.
If you control the form’s markup, you don’t need a hook at all: add the class aftersell-cart-skip-atc to the <form> and Aftersell leaves it alone. Use this hook when you can’t edit the markup, or when the decision depends on something only your code knows.
Use it for: a pre-order or quote form that needs its own redirect, a subscription app’s custom flow, a “buy it now” button that should go straight to checkout. To turn interception off for the whole page instead, use skip_add_to_cart_interceptor, but prefer this hook, which is scoped to the forms you name.

Where to go next

  • Cart object: the shape of the line a transform receives.
  • Events: everything you can subscribe to.
  • Actions: reading and changing the cart.
  • Use cases: complete solutions to common requests.