Shopify pack margin before you publish
A spreadsheet rule for deciding whether a Shopify Functions pack still earns contribution margin after component COGS, gift absorption, fees, and the pack discount — with free-item and spread failure cases, and fixed thresholds for publish, reprice, or kill.
Jeshua Leger
Founder, Leger Studio ·
A pack passes its checkout test, the gift line reads $0.00, the kit sums to the cent, and it goes live on Friday. Three weeks later finance asks why gross profit fell while bundle revenue rose. Nothing in checkout was wrong. The offer was priced against the shelf and never against cost, and it was quietly converting full-price hoodie buyers into hoodie-plus-gift buyers at the same $60.
This post does one job: before you publish a Functions pack, decide with a fixed rule — not a feeling about the offer — whether it still earns contribution margin after component COGS, gift absorption, and the pack discount, and what to do when it fails. It does not cover how the savings land on component lines (allocation math), how to build a kit at a round price (fixed-price packs), or what other promotions may touch the order once it is live (discount stacking). Those assume the offer deserves to exist; this is where you find out.
Shelf price is not contribution margin
Every pack has a natural sum (components at their own Shopify prices), a pack price, and the gap between them — the pack discount. Merchants price the gap: “$20 off,” “stand free,” “kit for $80.” The shopper pays the pack price and is done. You still owe the cost of every component in the box, the payment fee on the pack price, whatever shipping the pack makes you absorb, and the returns it will generate. What is left is the pack’s contribution. Two facts about it are not obvious from the editor:
- Allocation does not change it. Free-item and spread mode put the same pack discount on different lines; cash in and cost out are identical. What allocation changes is which line carries the discount — and therefore what a partial refund costs, what per-SKU reports show, and which pricing mode fits the offer. The line-level failures below come from that, not from the total.
- The pack competes with an order you were already going to get. Some share of its buyers would have bought the hero at full price; against those orders the pack is a discount, not an upsell. The publish decision is the pack’s contribution against the order it replaces, weighted by how many buyers it actually adds.
Five inputs, all from your own admin
One sheet row per pack, with these columns:
- Component natural prices and COGS. Prices are what the component variants sell for solo. COGS is landed cost — Shopify’s Cost per item field on each variant if you keep it current, otherwise your last PO plus inbound freight per unit. If you do not know a component’s cost to within a dollar, stop here; every number downstream is a guess.
- Pack price and pricing mode. Fixed price is the number you typed; percentage or amount off is computed from the natural sum; item-based pricing sets the discount equal to the free items’ natural prices. Write the resulting pack price and the discount percent d = discount ÷ natural sum — d is what the spread test reads.
- Variable costs the pack adds. Payment processing on the pack price (2.9% + 30¢ is a common card rate; use yours). Shipping you subsidize when the pack crosses your free-shipping threshold and the hero alone does not. A returns allowance — your category return rate times the pack price times the share you cannot resell at full value.
- The order it replaces. Name the single product most pack buyers would have bought instead — usually the hero — and compute its contribution the same way.
- Your thresholds. Two numbers you pick once and write into the sheet header: the largest share of pack buyers you will assume are incremental without a holdout test, and the share above which you kill instead of reprice. The rule below uses 25% and 50%. Pick yours before you look at any specific pack; once you have fallen for an offer you will move them.
Two derived numbers: CM(pack) = pack price − ΣCOGS − fee − shipping subsidy − returns allowance, and CM(hero) computed the same way. If CM(pack) is lower, the breakeven incremental share is s = (CM(hero) − CM(pack)) ÷ CM(hero): the fraction of pack buyers who must be people who would not have bought otherwise, just for the pack to hold contribution flat.
Free-item packs: the gift’s COGS is the price of the offer
Hoodie $60, COGS $22. Stand $20, COGS $9. Pack price $60 with the stand marked free — item-based pricing, so the discount is exactly the stand’s $20 and the order reads Hoodie $60, Stand $0.00. Card fee on $60 is $2.04. Hero alone: 60 − 22 − 2.04 = $35.96. Pack: 60 − 31 − 2.04 = $26.96. The pack earns $9.00 less than the hoodie sold alone, and $9.00 is the stand’s COGS. That is the general result: when the pack price equals the hero price, the shopper pays what they were paying, so the entire cost of a free-item offer is the gift’s landed cost plus whatever it adds to the parcel. Breakeven share s = 9.00 ÷ 35.96 = 25%. One in four pack buyers must be someone the hoodie alone would not have converted. With a 25% threshold this publishes, on the line.
Now swap the stand for a $25 accessory with COGS $16. Natural sum $85, pack still $60, discount $25. Pack: 60 − 38 − 2.04 = $19.96. Gap to the hero $16.00, s = 44%. Same hero, same pack price, same “free gift” copy; the offer moved from publish to reprice because one COGS figure changed, and nobody on the marketing side sees COGS.
Spread packs: a fair-share line can sit below its own cost
Serum $50, COGS $12. Cream $30, COGS $11. Mini $20, COGS $8. Fixed $80 kit, spread allocation: d = 20%, lines $40 / $24 / $16, every line 20% off to the cent. Total COGS $31, fee $2.62, pack contribution $46.38. The serum alone earns 50 − 12 − 1.75 = $36.25. The pack out-earns the order it replaces, so it publishes even if every buyer would have bought the serum anyway — a true upsell, adding $10.13 per order at 100% cannibalization. Add an $8 label if the kit crosses your free-shipping threshold and the serum does not, and it still clears at $38.38.
Now swap the mini for a $20 applicator tool you resell at COGS $17. Aggregate: COGS $40, contribution 80 − 40 − 2.62 = $37.38, still above the hero. Line level: the tool’s fair share is $16 against a $17 cost, so every kit invoices that line a dollar below what you paid for it. The pack passes the total test and fails the line test, and the line test is the one that shows up later: a shopper who returns the serum and cream and keeps the tool is refunded $56 on an $80 order, and you kept $16 for a $17 item (refund basis is the allocated line); your per-product profit report shows the tool at negative margin, and someone proposes discontinuing a product that only loses money inside this kit.
Because spread mode gives every component the same percentage off, the line test is one comparison per component: its gross margin percent, 1 − COGS ÷ price, must exceed the pack discount percent d. The tool’s margin is 15%; d is 20%; the tool goes underwater on every order. Percentage-off pricing makes d explicit in the editor; fixed and amount-off pricing hide it, and d moves whenever a component’s solo price moves — reprice a component and rerun the row.
The publish / reprice / kill rule
Run these in order for every pack row. The first line that fires is the decision.
- CM(pack) ≤ 0 after fees, shipping, and returns → kill. Volume does not fix a negative unit contribution; it multiplies it.
- Free-item packs: discount > free items’ natural prices → fix pricing first. You are pricing a gift and a markdown; move to item-based pricing or raise the fixed price until the discount fits inside the gifts.
- Spread packs: any component with margin percent < d → reprice or restructure, regardless of the total. Remove the component, raise the pack price until d drops below the thinnest component’s margin, or rebuild as free-item with the thin component at natural price and a high-margin component as the gift.
- CM(pack) ≥ CM(hero) → publish. The pack out-earns the order it replaces at any cannibalization rate.
- s ≤ 25% → publish, and track the two numbers in the next section from day one.
- 25% < s ≤ 50% → reprice. Do not publish to “see how it does”; a pack at s = 40% loses contribution on most of its orders while its revenue chart goes up.
- s > 50% → kill this version. Half of buyers being net new is a claim you cannot support without a holdout test, and a pack that only works under that claim is a markdown with a story.
The 25% and 50% are the numbers I would write; the rule works because they are written down before you price the pack, not because they are exactly right. If you later run a holdout — pack visible to half of traffic for two weeks, hero-only for the other half — replace the threshold with the incrementality you measured and keep the structure.
Reprice moves that keep the offer alive
A failing row is usually one input away from passing. In rough order of how little the shopper notices:
- Raise the pack price and leave the natural sum alone. The $25-accessory pack at $68 still shows the shopper $17 off an $85 pair; contribution becomes 68 − 38 − 2.27 = $27.73, s = 23%, and the row publishes. Every dollar of pack price is a dollar of contribution; every dollar of shelf discount is not.
- Swap the gift for one with lower COGS at a similar natural price. The copy does not change; the cost of the offer does. Free-item mode is the right allocation exactly when the gift’s landed cost is small next to the hero’s contribution — a decision rule, not a preference.
- Take the thin component out of a spread kit and sell it at natural price alongside it.
- Change the pricing mode, not the price. A fixed kit whose d crept above a component’s margin after a component reprice can be re-expressed as percentage off at a d you chose, so the next component price change does not silently move the discount.
What does not count as a reprice: adding a second component to “add value,” which adds COGS to a row that already fails on COGS, or gating the pack behind a code, which changes who sees the offer but not what each order earns.
After publish: two numbers, then the handoffs
The sheet made a bet about the hero and about incrementality. Check it against real orders inside the first two weeks:
- Hero solo units, before vs after. If the hero’s solo sales fell by roughly the number of packs sold, cannibalization is near 100% and the real s is near zero — a pack that published on s ≤ 25% is now a markdown. If solo units held and packs sold on top, the row was conservative. Better Bundles’ sales reports give bundle units sold, discounts given, and units per order attributed from paid orders (Analytics on Growth and Pro, CSV export on Pro); Shopify’s sales-by-product report gives the hero side.
- Realized contribution. Multiply bundle units by the row’s CM(pack) and put it next to what the same period would have earned on the hero alone. That is the number to bring to whoever asked why gross profit moved.
Then hand three things to the posts that own them. Pack availability follows real component stock, so a gift stockout takes the pack offline rather than shipping it short — decide whether the gift is reordered on the pack’s velocity or the pack pauses (kit inventory sync). Partial returns refund the allocated line price, which is why the spread line test matters after launch (returns and partial refunds). And any sitewide sale that reaches the pack re-runs this sheet at a deeper discount — exclude the pack from the sale’s scope or recompute the row at the stacked price (discount stacking).
Build the pack that passed the sheet
The rule only works if the pack you publish is the pack you priced — same components, same pricing mode, same allocation. Better Bundles makes that easy to hold to: pick fixed, percentage, amount-off, or item-based pricing, choose free-item or spread allocation, and check the live preview of how the discount lands on each component against your sheet before you publish. Basic is free forever with up to 3 active bundles — enough to publish the one row that passed and watch the two numbers; Starter, Growth, and Pro are $17 / $37 / $77 a month with a 15-day trial when you need more bundles or the sales reports. Start with create your first bundle, set the mode from discount allocation & application modes, and keep the sheet next to the editor. A pack that cannot pass a row with your own COGS in it should not pass checkout either.