How a trigger line is marked
A trigger line is a normal paid line the shopper added. CartSella tags it so the gift can be linked back to what earned it._trigger_offer_id is the reliable one. It is written when the line is added, and the gift sync engine also self-heals it on later passes: if a line qualifies for an active offer and is missing the tag, it gets patched in, no matter how the line reached the cart. _cs and _cs_role are only written on the add path, so a line the theme added without CartSella seeing the request can carry _trigger_offer_id and nothing else._trigger_offer_id to exist for them.
How a reward line is marked
Every gift line carries these:Why _cs_gift_for exists
Shopify merges two cart lines into one when they have the same variant and identical properties. If one offer has two different trigger products in the cart and both earn a gift of the same variant, those gifts would collapse into a single line of quantity 2, sitting nowhere near either trigger.
So CartSella tags each gift line with the variant id of the trigger product that earned it. Different tag, different properties, separate lines, each landing next to its own trigger.
The tag is only added when it is needed. With a single trigger product, or a spend threshold, the gift line has no _cs_gift_for at all. This is deliberate: tagging the common case would churn every existing untagged gift line into a remove-and-re-add.
How many gifts
The main condition multiplies. Buy N of the trigger product for one gift, and 2N earns two, 3N earns three. With several trigger products required together, the offer takes the lowest complete set count across them. A minimum cart total is a gate, not a multiplier: it either lets the gift through or blocks it, unless the merchant explicitly turned on “repeat the gift for every multiple of this amount”. The total is then split round-robin across the trigger products present, which is where_cs_gift_for comes from.
Legacy property names
CartSella renamed its properties for merchant-facing tidiness. The old names are read forever, not for a migration window. A shopper who left a tab open before the rename keeps running the cached script and keeps writing the old names, so a live cart can contain either format at any time.
Every recipe below reads both.
The cart note attribute
Attribution for the order lives on the cart, not on a line:_cs_attr, a JSON array with short keys.
t is entity_type, i is entity_id, st is entity_subtype, s is source. The full key table is on Cart line properties. Long key names are accepted on read, so a parser should try t then entity_type.
Recipes
Detect a gift line and render it differently
Drop this into your cart line snippet, wherever your theme rendersitem. It hides the price, shows a badge, and replaces the quantity stepper with a static label.
snippets/cart-line-gift.liquid
Hide CartSella properties from the property loop
The single most common thing a theme needs. Every CartSella property starts with an underscore, and the standard Shopify convention is to skip those, so a well-behaved theme already hides them. Check yours:Read gifts and their triggers in JavaScript
Fetches/cart.js, finds every gift, which offer produced it, and which line triggered it.
assets/my-cartsella-helpers.js
Re-run your code when the cart changes
CartSella fires these on bothdocument and window after every mutation it makes.
cart:refresh and cart:change carry the same payload. Pick one.
Rules you must not break
Do not strip or rename these properties on a line
Do not strip or rename these properties on a line
The gift line stops being recognised. The sync engine no longer sees it as its own, so it will not reconcile it, and the discount Function at checkout will not match it either. The shopper is charged full price for a gift the storefront told them was free.This includes “cleaning up” properties in a cart form, rebuilding a line through
/cart/add.js with your own property object, or filtering properties before a /cart/change.js patch. Hiding a property from the UI is fine. Removing it from the line is not.Do not add gift lines yourself
Do not add gift lines yourself
CartSella computes the correct gift quantity from the cart on every change and reconciles the cart to that number. A line you add will be removed on the next pass, or worse, will collide with a line CartSella adds and leave the shopper with two gifts, only one of which the discount Function frees.If you want a gift added, configure the offer. If you want a paid product added, add it without CartSella properties and let the engine tag it if it qualifies.
Always send quantity when patching a line with /cart/change.js
Always send quantity when patching a line with /cart/change.js
/cart/change.js assumes quantity: 1 when the field is omitted. A patch that only means to change properties therefore silently deletes the shopper’s other units: a line of 3 comes back as a line of 1.Gift with purchase lines are locked against shopper removal, other reward types are not
Gift with purchase lines are locked against shopper removal, other reward types are not
CartSella intercepts cart mutations and clamps them. A quantity increase on any reward line is reverted to the line’s current quantity. A decrease or removal on a gift with purchase reward line is reverted too. Your remove button on such a line will appear to do nothing, by design: the engine reconciles the gift from the trigger, so the way to remove the gift is to remove the trigger.If a removal does reach Shopify through a path CartSella did not intercept, the offer id is recorded in the
_cs_removed_gifts cart attribute so the sync does not immediately re-add the gift. That marker is cleared once the trigger goes away or the shopper adds the gift back.Reward lines of the other types (buy_more_save_more, fixed_bundle, pick_your_bundle, bundle_builder) are freely removable. That is deliberate: they have no reconciliation loop, so locking them would strand an orphaned free line forever once its trigger left the cart. Removing a bundle’s free line does mean the shopper loses the discount on the paid lines of that bundle too, because the Function no longer matches the set.Never reorder the shopper's paid lines
Never reorder the shopper's paid lines
Reordering means removing and re-adding, which is a data-loss risk on the shopper’s own products and changes which lines a bundle Function matches. CartSella positions gift lines by controlling the order things are added, never by rewriting existing lines.If a gift is not visually next to its trigger in your theme, reorder in your rendering, not in the cart.
See also
- Cart line properties for the complete property and note attribute reference.
- Gift with purchase for the merchant-facing side: how the offer is configured and what each setting does.