Shopify pack analytics after checkout expansion
When a pack expands into components at checkout, the storefront counts the pack, the order carries the components, and the app counts pack units from paid orders — three numbers that disagree for a reason. Which one to use for kill/keep, which for component reorder, which for ads, and the one test order that reconciles them.
Jeshua Leger
Founder, Leger Studio ·
The Travel Set launched three weeks ago. Marketing says it converts: sessions on its product page are up, add-to-cart rate is above the hero’s, the ad set pointing at it has the cheapest add-to-cart in the account. Ops says it is not selling: Sales by product shows the Travel Set at zero units, and the same report shows the travel bag — a component nobody promotes — suddenly moving forty units a week. Finance asks which one is right so they can decide whether to reorder bags or kill the set. Both reports are right. They are counting different objects, and the meeting is trying to make one number do three jobs.
This post is about that meeting. A pack that expands into its real components at checkout produces three different pictures of itself — the product the customer chose, the lines the order actually carries, and the app’s count of packs on paid orders — and each is the right input for exactly one kind of decision. It is not the post about pricing the pack before launch (pack margin), about what the warehouse prints (pack lines your 3PL can pick), or about what support tells a customer who sees component lines (briefing CS). Those assume you already know which report to open. This one is the map.
One purchase, three objects
Follow a single Travel Set order through the admin and the disagreement stops looking like a bug.
- Before checkout, the object is the pack. The session landed on the Travel Set’s page, the product view and add-to-cart events fired against the Travel Set’s product ID, and any ad or email pixel wired to those events recorded the Travel Set. Storefront behaviour reports — sessions, conversion by landing page, add-to-carts, marketing attribution — see a pack product, because that is what the customer chose.
- On the order, the objects are the components. Checkout expanded the pack, so the order’s line items are the serum, the case, and the bag, each with its own SKU, quantity, and price — grouped under the Travel Set as a bundle parent, but the pack itself is not an order line. Every Shopify report built from order lines inherits this: Sales by product, by variant, by SKU, best-sellers, collection performance, and inventory movements all credit the components.
- In the app, the object is the pack again — but counted from paid orders, not from sessions. Better Bundles reads the orders it expanded and reports units sold and discounts given per bundle over the selected window. It cannot see a session or an add-to-cart; it only sees what was paid for.
So the Travel Set at zero in Sales by product is not a broken report — it is the correct answer to a question you did not mean to ask. Forty bags a week is also correct: they shipped. And the ad set’s cheap add-to-carts are real, but they are add-to-carts of the pack, which is a different number from paid packs. The failure is treating a number from one surface as if it measured the object from another.
Kill or keep: paid packs, then the hero’s solo line
The kill/keep question is “does this offer earn its discount?” — and it has to be answered on the pack, on paid orders. That rules out two tempting sources. Storefront metrics are demand, not revenue: a pack with a great add-to-cart rate and a poor checkout rate is a pack customers liked until they saw the expanded lines or the shipping quote. And Sales by product is the wrong object entirely — the pack row is zero by construction, and the component rows mix pack demand with standalone demand.
- Units: bundle units sold from the app’s Reports page (reports & analytics). This is the count of packs that were paid for in the window, which is the only unit that means “the offer worked.”
- Cost: discounts given for the same bundle and window, from the same page. Divide by units for the discount per pack; put it next to the contribution-margin row you priced the pack on. If you did not price it on a row, that is the margin post’s job.
- Cannibalization: the hero’s solo units, before vs after launch, from Shopify’s Sales by product or by variant. Here the component report is exactly right, because you want the hero’s total movement — and if the hero’s row rose by roughly the pack’s unit count while its standalone sales fell by the same amount, the pack is moving hero demand sideways at a discount rather than adding orders. If your store does populate Shopify’s bundle comparison report, its “Is bundle” split gives you this directly; if not, before/after on the solo line is the honest version.
Component reorder: order lines already include the pack
Reorder is the decision where the expanded order lines are not a problem but the point. Because checkout expands the pack, every bag that left in a Travel Set is already a bag in Sales by variant, in the variant’s inventory history, and in whatever velocity your replenishment uses. The component report is the complete picture of bag demand — solo plus every pack that includes it — without anyone adding anything.
The failure mode here is the opposite of kill/keep: double counting. A planner sees forty bags a week, then opens the pack report, sees thirty packs a week, and adds thirty bags to the forecast “for the bundle.” Those thirty are already inside the forty. The pack report tells you why bag demand rose; it is not a second source of it.
- Reorder points and velocity for a component come from the component’s own order lines. Do not add pack units on top.
- Use pack units to explain a step change, not to size it. If bag velocity jumped from ten to forty when the set launched, the pack report tells you the thirty are pack-driven — and therefore tied to the pack’s promotion calendar, not to organic bag demand. Size safety stock for what happens when the pack is paused, not just for the current rate.
- For a planned pack promotion, forecast at the component level: expected pack units × quantity of that component per pack, added to each component’s standalone forecast. That is the one place pack units legitimately enter a reorder — as a forward input, never as a backward correction.
- Pack availability follows component stock, so the first component to run short takes the pack offline rather than overselling (inventory sync). That protects the customer, but it also means a pack’s sales report goes quiet for a stock reason, not a demand reason — check component availability before you read a falling pack line as a falling offer (kit inventory sync).
Ads and ROAS: attribute at the order, not the product row
Ad reporting is where the object switch hurts most, because the pack is the product the campaign promoted and the product that vanishes at checkout. Upper-funnel events — view content, add to cart — fired for the pack. The order that resulted carries components. Depending on how a pixel or catalog integration reads the order, a product-level ROAS view can show the Travel Set campaign converting nothing while three components convert from traffic nobody bought for them, and a catalog optimiser can learn from that.
- Judge a pack campaign at the order level: campaign → session → order → order value. Shopify’s marketing attribution and UTM-based order reporting are order-scoped and do not care that the order’s lines are components. A product-level breakdown of that same campaign is the report to distrust.
- For a pack’s own ROAS, use paid packs in the campaign window — the app’s bundle units for the date range — against the campaign’s spend, and sanity-check it against orders whose lines are grouped under that pack. Add-to-carts of the pack are the diagnostic between the two: high add-to-carts with few paid packs is a checkout-page problem (expanded lines the customer did not expect, shipping on component weight), not a targeting problem.
- Before comparing “pack ad vs hero ad,” measure both sides on the same object. Comparing the hero’s Sales by product row to the pack’s Sales by product row compares a real number to a structural zero.
- Check what your purchase event sends. Run the test order below with the pixel’s event debugger open and see whether the purchase reported the pack’s product ID or the components’. If it is the components, either accept order-level reporting for pack campaigns or fix the mapping — but stop reading product-level ROAS for the pack until you have.
Where the discount shows up depends on the mode you chose
One more place the surfaces disagree, and this one is a configuration choice. With product-level application — the default — each component line carries its allocated price, so a spread pack shows every line a little below its product-page price and a free-item pack shows the gift at $0.00. With order-level application (Growth and Pro), the lines stay at their natural prices and the pack’s saving appears once as an order discount (discount allocation & application modes).
Finance reading Shopify’s reports sees these differently: product-level application puts the pack’s cost into each component’s line price, so it reads as a per-SKU markdown; order-level application keeps the lines at natural prices and shows the cost in the order’s discount column. Neither is wrong. The app’s discounts given figure is the pack’s total saving from paid orders regardless of how the order displayed it, so if you switch a pack from product-level to order-level mid-quarter, the app’s number keeps measuring the same thing while Shopify’s per-SKU net moves for a presentation reason. Note the switch date on the campaign card, or the component margin trend will look like it recovered when only the display changed.
The one test order that reconciles all three
Do this once per pack before you let any of its numbers into a decision. Place one real, paid order for the pack — a staff order is fine — and then read that single order in every surface.
- Open the order in the admin. Confirm the lines are the components, each marked as part of the pack, at the prices your mode predicts (allocated, or natural plus one order discount). Write down the component quantities — this is what every order-line report will count.
- Open Sales by product (or by variant) for that day. The pack row should be absent or zero; each component row should have moved by exactly the quantity you wrote down. If a component did not move, the report is not the problem — find out why the line did not carry the SKU you expected.
- Open the app’s Reports page for the same day. Bundle units sold should read 1 and discounts given should equal the saving you configured. If your store shows data in Shopify’s bundle reports, note whether this order appears there; if it does not, that report is not an input for this pack.
- Open your storefront and marketing reports for the session. Confirm the add-to-cart was recorded against the pack and the order is attributed to the session’s source. If you run a pixel, check which product IDs the purchase event carried.
- Write the four results on the pack’s campaign card: object counted per surface, quantities per component, where the discount appears, what the purchase event sent. Anyone who reads the card can now open the right report for the question they have.
After that order, the reconciliation check for any window is arithmetic: app pack units × each component’s quantity per pack should account for the pack-driven part of that component’s order-line volume. If the component moved less than that, look for cancelled or unpaid orders; if it moved more, the rest is standalone demand, and you have just separated the two numbers the meeting was arguing about.
The short version for the campaign card
- Kill/keep → paid pack units and discounts given (app), plus the hero’s solo line before vs after (Shopify).
- Component reorder → component order lines and inventory history (Shopify); pack units explain or forecast, never add.
- Ads / ROAS → order-level attribution and paid packs in the window; pack add-to-carts diagnose the gap.
- Discount cost → the app’s discounts given, with the application mode and any switch date noted.
- Before trusting any of them → one paid test order read in all four places.
Better Bundles expands every pack into its real component lines at checkout on every plan, so the order-side numbers in this post are what your warehouse and your Shopify reports already see. The app’s Reports page adds the pack-side count — units sold and discounts given per bundle from paid orders, with 30-day history on Basic and Starter, 90 days on Growth, 365 days and CSV export on Pro. Basic is free forever with up to 3 active bundles; Starter, Growth, and Pro are $17 / $37 / $77 a month with a 15-day trial. Build the pack in create your first bundle, place the test order, and put the card together before the first weekly review argues about it.