<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Ninety9 — Shopify AOV &amp; CRO blog</title>
    <link>https://ninety9.dev/blog/</link>
    <atom:link href="https://ninety9.dev/rss.xml" rel="self" type="application/rss+xml"/>
    <description>Ninety9 builds conversion-rate optimisation apps for Shopify: AI cart drawers, bundles, upsell popups, free shipping progress bars and post-purchase order editing. Trusted by 20,000+ stores.</description>
    <language>en-us</language>
    <lastBuildDate>Tue, 18 Aug 2026 03:48:48 GMT</lastBuildDate>
    <generator>Ninety9 static site builder</generator>
    <image><url>https://ninety9.dev/assets/img/brand/ninety9-logo.png</url><title>Ninety9</title><link>https://ninety9.dev/</link></image>
    <item>
      <title>How to Increase Average Order Value on Shopify (The Complete 2026 Playbook)</title>
      <link>https://ninety9.dev/blog/how-to-increase-average-order-value-shopify.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/how-to-increase-average-order-value-shopify.html</guid>
      <pubDate>Sun, 16 Aug 2026 09:00:00 GMT</pubDate>
      <category>Average Order Value</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>A practical, maths-first guide to raising Shopify AOV — the eleven levers that work, where each one belongs in the funnel, and how to tell which is worth your next week.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>AOV is the only growth lever that costs nothing per additional order and compounds across every channel you already run.</li><li>The eleven working levers split into four funnel positions — product page, cart, exit, post-purchase — and you should own all four eventually, one at a time.</li><li>Measure AOV alongside conversion rate and margin per order. A tactic that lifts AOV while dropping conversion or margin has made you poorer.</li><li>Free shipping thresholds and quantity breaks are the fastest wins; personalised bundles and post-purchase upsells have the highest ceiling.</li></ul></section>
<p>There are exactly three ways to make an ecommerce store bigger. Get more visitors. Convert more of them. Or make each order worth more.</p>
<p>The first is a budget question and gets more expensive every year. The second is real work with a hard ceiling — most categories top out somewhere between two and four percent, and getting from 2.1% to 2.4% takes months of testing. The third is the one most stores treat as an afterthought, and it is the only one where the improvement is permanent, applies to every channel simultaneously, and costs nothing per additional order.</p>
<p>This guide is the complete version: the eleven levers that reliably work, where each belongs, and how to decide which one deserves your next week.</p>
<h2 id="the-maths-that-makes-this-worth-reading">The maths that makes this worth reading</h2>
<p>Take a store doing 1,000 orders a month at a $58 average order value. That is $58,000 in monthly revenue.</p>
<p>Now add $9 to the average order. One accessory, one quantity break, one nudge across a free shipping line. Same traffic, same conversion rate, same ad spend.</p>
<div class="tablewrap"><table><thead><tr><th></th><th>Before</th><th>After +$9 AOV</th><th>After +15% conversion</th></tr></thead><tbody>
<tr><td>Orders / month</td><td>1,000</td><td>1,000</td><td>1,150</td></tr>
<tr><td>Average order value</td><td>$58</td><td>$67</td><td>$58</td></tr>
<tr><td>Revenue</td><td>$58,000</td><td>$67,000</td><td>$66,700</td></tr>
<tr><td>Extra ad spend</td><td>—</td><td>$0</td><td>~$4,400</td></tr>
<tr><td>Extra gross profit (at 55% margin)</td><td>—</td><td><strong>$4,950</strong></td><td><strong>$400</strong></td></tr>
</tbody></table></div>
<p>Both columns produce roughly the same revenue. Only one of them produces meaningful profit, because a conversion-rate win still carries the acquisition cost of the extra sessions, while an AOV win carries almost no marginal cost at all.</p>
<p>That asymmetry is the entire argument. It is also why AOV work is usually the highest return-on-effort project available to a store doing more than a few hundred orders a month.</p>
<h2 id="measure-three-numbers-not-one">Measure three numbers, not one</h2>
<p>The single most common mistake is optimising AOV in isolation. Every tactic below can be gamed into a higher average order value while making the business worse. Watch all three of these together:</p>
<ol>
<li><strong>Average order value</strong> — revenue ÷ orders, for a fixed window.</li>
<li><strong>Conversion rate</strong> — sessions that end in an order. If a tactic adds friction before commitment, this drops.</li>
<li><strong>Gross profit per order</strong> — revenue minus COGS minus discount minus shipping cost, ÷ orders. This is the number that pays your rent.</li>
</ol>
<p>A bundle that lifts AOV from $58 to $71 by giving away 30% is often a loss. A free shipping threshold set below your true fulfilment cost raises AOV and destroys margin. Always run the third number.</p>
<aside class="callout callout--key">
<p class="callout__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 7a5 5 0 1 0-4.5 5H12v3h3v3h4v-4l-4-4V7Z"/></svg>Your success metric</p>
<p>Gross profit per session. It captures conversion rate, order value and margin in one figure, and it is almost impossible to game.</p>
</aside>
<h2 id="the-four-positions">The four positions</h2>
<p>Every AOV tactic sits at one of four points in the journey. They behave differently and they compound, because a shopper who adds an accessory on the product page can still cross a shipping threshold in the cart and still accept a post-purchase offer.</p>
<div class="tablewrap"><table><thead><tr><th>Position</th><th>Shopper state</th><th>What works here</th><th>Risk to conversion</th></tr></thead><tbody>
<tr><td>Product page</td><td>Still deciding</td><td>Bundles, quantity breaks, frequently bought together</td><td>Medium — can add friction</td></tr>
<tr><td>Cart</td><td>Committed to buying</td><td>Progress bars, in-cart upsells, gift thresholds</td><td>Very low</td></tr>
<tr><td>Exit / checkout start</td><td>Leaving or converting</td><td>Exit-intent offers, add-to-cart popups</td><td>Low if intent-triggered</td></tr>
<tr><td>Post-purchase</td><td>Already paid</td><td>Order edits, upsells on the confirmation and edit screens</td><td>Zero</td></tr>
</tbody></table></div>
<p>Most stores start at the cart, because the risk is lowest and the effect is quickest. That is the right instinct.</p>
<h2 id="lever-1-the-free-shipping-threshold">Lever 1: The free shipping threshold</h2>
<p>The highest-leverage single change most stores can make, and the one most often set by guesswork.</p>
<p>Free shipping is not a discount; it is a <em>goal</em>. It converts a passive basket into an active one by giving the shopper a number to reach. The mechanic works even when the shipping fee it replaces was small, because the shopper is no longer weighing $6.90 against convenience — they are chasing a finish line.</p>
<p>Setting the number properly:</p>
<ul>
<li>Pull your order value distribution for the last 90 days, not your average. The average hides the shape.</li>
<li>Find the 60th–75th percentile of order value. Your threshold belongs in that band.</li>
<li>Sanity-check against margin: the gross profit on the incremental spend must exceed your average shipping cost.</li>
<li>Set it per country. A €75 threshold in Germany and a €75 threshold in Bulgaria are not the same offer.</li>
</ul>
<p>A threshold set at your median order value is too low — most shoppers cross it without changing behaviour and you have simply given away shipping. A threshold set at twice your median is too high — it reads as unreachable and shoppers disengage from the goal entirely.</p>
<h2 id="lever-2-the-progress-bar">Lever 2: The progress bar</h2>
<p>The threshold does nothing if the shopper cannot see how close they are. A progress bar turns an abstract policy line into a live, personal, incomplete task — and incomplete tasks are uncomfortable in a way that reliably produces one more item.</p>
<p>Three things separate a bar that works from a bar that decorates:</p>
<ul>
<li><strong>It must show the remaining amount, in the shopper&#39;s currency.</strong> &quot;Spend $23 more for free shipping&quot; beats &quot;Free shipping over $75&quot; by a wide margin, because one is about them and the other is about you.</li>
<li><strong>It must appear where the decision happens.</strong> The cart drawer first, the cart page second, the product page third. A sticky top bar is useful reinforcement but rarely the primary driver.</li>
<li><strong>It must celebrate the crossing.</strong> The state change when the goal is met is what makes the next goal credible.</li>
</ul>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/goalify/icon.png" alt="Goalify: Free Shipping Bar PRO app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Goalify: Free Shipping Bar PRO</p>
<p class="appcta__text">Free shipping bars, free gifts, BOGO and AI upsells driven by stackable cart goals.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/goalify?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="lever-3-stacked-goals">Lever 3: Stacked goals</h2>
<p>One threshold captures one behaviour change. A shopper who lands at $76 on a $75 free shipping threshold has no reason to go further, and you have left the entire upper half of your distribution untouched.</p>
<p>Stacked goals fix this by revealing the next target the moment the current one is met:</p>
<ul>
<li>$50 → free shipping</li>
<li>$85 → 10% off the order</li>
<li>$130 → free gift</li>
</ul>
<p>Each threshold is placed at a percentile of your distribution rather than at a round number that felt nice. The result is a mechanic that keeps working across the whole range of basket sizes instead of only at one point on it.</p>
<p>The margin discipline here is straightforward: the cost of each reward must be less than the gross profit on the incremental spend required to unlock it. Model each tier separately.</p>
<h2 id="lever-4-quantity-breaks">Lever 4: Quantity breaks</h2>
<p>The simplest bundle there is: buy two, save 10%; buy three, save 15%. No pairing logic, no catalogue work, no design decisions beyond a table.</p>
<p>Quantity breaks work best on consumables, anything with a predictable replenishment cycle, and anything where the shopper&#39;s real question is &quot;how many&quot; rather than &quot;which one&quot;. They work poorly on considered single purchases — nobody wants two sofas.</p>
<p>The maths matters more than the design. A 15% discount on three units is only worth taking if your gross margin comfortably exceeds it <em>and</em> the customer would not have bought three units anyway. Cannibalisation is the real cost of a quantity break, and it is invisible in the AOV number.</p>
<h2 id="lever-5-frequently-bought-together">Lever 5: Frequently bought together</h2>
<p>The classic, and still the highest-converting product-page upsell when the pairs are right.</p>
<p>The mistake is picking pairs by category. A phone case and a phone are the same category; a phone case and a screen protector are the pair that sells. The only reliable source for good pairs is your own order history — which products actually appear in the same basket, at what frequency, in what order.</p>
<p>Rules of thumb that hold up:</p>
<ul>
<li>Two or three suggestions, never a grid of eight. Choice paralysis is real and it costs conversions.</li>
<li>The suggested item should be meaningfully cheaper than the anchor product. Roughly 15–40% of the anchor price is the comfortable zone.</li>
<li>One-click add, no page reload, no navigating away from the product being considered.</li>
</ul>
<h2 id="lever-6-cross-sells-in-the-cart-drawer">Lever 6: Cross-sells in the cart drawer</h2>
<p>The cart is the most under-used surface in ecommerce. The shopper has committed. There is no risk of losing the sale by showing them something else, because the decision to buy has already been made.</p>
<p>A good cart drawer does four jobs at once: it shows the contents, it shows progress toward a goal, it offers one or two relevant additions, and it removes uncertainty about delivery. Most default theme carts do only the first.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/addy/icon.png" alt="Addy: AI Cart Drawer &amp; Bundles app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Addy: AI Cart Drawer &amp; Bundles</p>
<p class="appcta__text">AI cart drawer with in-cart upsells, volume bundles, free shipping bars and per-Market targeting.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/addy-cart-drawer?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="lever-7-product-add-ons">Lever 7: Product add-ons</h2>
<p>Warranties, gift wrap, shipping protection, express handling, engraving, a spare part. Add-ons are unusually profitable because their cost of goods is often near zero and they do not cannibalise anything.</p>
<p>The rule is that an add-on must be genuinely optional and genuinely useful. Shipping protection that is pre-ticked is a dark pattern, will generate chargebacks, and in several jurisdictions is now illegal. Shipping protection offered honestly, unticked, with a clear explanation, converts at a rate that will surprise you.</p>
<h2 id="lever-8-intent-triggered-popups">Lever 8: Intent-triggered popups</h2>
<p>Popups have a bad reputation because most of them are timed. A popup on a five-second timer interrupts a shopper who is still deciding, which is precisely the wrong moment.</p>
<p>Intent triggers invert this:</p>
<ul>
<li><strong>Add to cart</strong> — fires after commitment. The wallet is already open and the shopper is in a buying frame of mind. This is the single best upsell slot most stores own.</li>
<li><strong>Checkout initiation</strong> — one last relevant offer before they leave the storefront.</li>
<li><strong>Exit intent</strong> — the shopper is leaving anyway. There is no conversion left to lose.</li>
</ul>
<p>None of these interrupt browsing, which is why they do not cost conversion rate the way timed popups do.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/monet/icon.png" alt="Monet • AI Popup Bundle Addons app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Monet • AI Popup Bundle Addons</p>
<p class="appcta__text">AI upsell popups triggered on add to cart, checkout initiation and exit intent.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/monet?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="lever-9-personalised-recommendations">Lever 9: Personalised recommendations</h2>
<p>Rules-based recommendations (&quot;customers who bought X also bought Y&quot;) are a good default. Learned recommendations that read your actual order history beat them once you have enough data, mainly because they keep working when your catalogue and season change and a static rule does not.</p>
<p>The honest caveat: below roughly 500 orders of history, a well-chosen manual pairing usually outperforms a model. Personalisation is a scale advantage, not a starting point.</p>
<h2 id="lever-10-post-purchase-offers">Lever 10: Post-purchase offers</h2>
<p>The confirmation page is the highest-intent surface in ecommerce and most stores put a tracking link on it. The shopper has just paid, the friction of entering payment details is behind them, and their disposition toward your brand will never again be as positive as it is in that moment.</p>
<p>Anything you can offer that does not require re-entering payment details will convert at rates that look like errors compared to pre-purchase offers.</p>
<h2 id="lever-11-order-editing-as-a-revenue-surface">Lever 11: Order editing as a revenue surface</h2>
<p>The most overlooked lever on this list. Between &quot;order placed&quot; and &quot;parcel shipped&quot; there is a window where the customer&#39;s needs can still change — and today, most stores handle that window with an email to support.</p>
<p>Letting the customer edit the order themselves does three things at once. It removes a support ticket. It prevents a cancellation, because a shopper who cannot fix a mistake will often just cancel the whole thing. And it puts a warm, engaged buyer on a page you control, where a relevant recommendation converts extremely well.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/reviso/icon.png" alt="Reviso: Order editing &amp; Upsell app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Reviso: Order editing &amp; Upsell</p>
<p class="appcta__text">Post-purchase self-service order editing with cancellation deflection and AI upsells.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/reviso-order-editor?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="what-to-do-first">What to do first</h2>
<p>Ordered by return on the time it takes:</p>
<ol>
<li><strong>This week</strong> — put a free shipping progress bar in the cart with a threshold set from your actual distribution.</li>
<li><strong>This month</strong> — add quantity breaks to your top ten SKUs by unit volume, and frequently-bought-together pairs derived from real order data.</li>
<li><strong>This quarter</strong> — replace the default cart drawer with one that does upsells and delivery estimates, and add an add-to-cart popup.</li>
<li><strong>Next quarter</strong> — build the post-purchase surface: <a href="/apps/reviso.html">order editing</a>, cancellation deflection and a confirmation-page offer.</li>
</ol>
<aside class="callout callout--tip">
<p class="callout__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18h6M10 22h4M12 2a7 7 0 0 0-4 12.7V17h8v-2.3A7 7 0 0 0 12 2Z"/></svg>Run one change at a time</p>
<p>AOV tactics interact. A progress bar and a quantity break launched in the same week cannot be attributed separately, and you will spend the next quarter arguing about which one worked. Two weeks per change at moderate volume is usually enough to see the signal.</p>
</aside>
<h2 id="the-honest-failure-modes">The honest failure modes</h2>
<p>Things that raise AOV and should not be done:</p>
<ul>
<li><strong>Pre-ticked add-ons.</strong> Illegal in the EU under the Consumer Rights Directive, and a reliable source of chargebacks everywhere else.</li>
<li><strong>Fake countdown timers.</strong> A timer that resets on refresh is a lie, it is enforceable as one, and shoppers notice more often than you think.</li>
<li><strong>Thresholds you cannot honour.</strong> A free shipping line that quietly excludes half your catalogue produces more support tickets than revenue.</li>
<li><strong>Six upsells on one page.</strong> Every additional offer dilutes the others and adds cognitive load. One good offer beats four mediocre ones every time.</li>
</ul>
<h2 id="where-this-leaves-you">Where this leaves you</h2>
<p>Average order value is not a growth hack; it is a structural property of how your store is built. Stores with high AOV are not lucky — they have deliberately designed four moments in the journey to make spending slightly more the natural thing to do.</p>
<p>Pick the moment that leaks the most, fix it properly, measure gross profit per session, and then move to the next one.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>What is a good average order value for a Shopify store?</summary><div class="faq__a"><p>There is no universal benchmark because AOV is a function of your price point. The number that matters is your own trend line and your AOV relative to blended customer acquisition cost. A store with a $40 AOV and a $12 CAC is far healthier than one with a $180 AOV and a $150 CAC. Track the ratio, not the absolute.</p></div></details>
<details><summary>How quickly can I increase average order value?</summary><div class="faq__a"><p>A free shipping progress bar or a quantity break can be live in under an hour and will usually show a measurable effect within two weeks at moderate order volume. Deeper work — bundle architecture, personalised recommendations, post-purchase flows — takes a quarter to tune properly but has a much higher ceiling.</p></div></details>
<details><summary>Does increasing AOV hurt conversion rate?</summary><div class="faq__a"><p>It can, if you add friction. Upsells that interrupt browsing, popups on a timer and aggressive cross-sell grids on the product page all cost conversions. Offers placed after commitment — in the cart, at add-to-cart, or post-purchase — almost never do. Always watch both numbers together.</p></div></details>
<details><summary>What is the difference between AOV and average basket size?</summary><div class="faq__a"><p>Average order value is revenue divided by orders. Average basket size usually means units per order. They move together but not always: a quantity break raises both, while an accessory upsell raises AOV more than units, and a bundle discount can raise units while flattening AOV.</p></div></details>
<details><summary>Should I raise prices instead?</summary><div class="faq__a"><p>Do both, but they are different instruments. A price rise applies to everyone and risks conversion across the board. AOV work is opt-in — only the shoppers who want more spend more — so it carries far less downside risk while producing a similar revenue effect.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>How to Set a Free Shipping Threshold That Increases Profit, Not Just AOV</title>
      <link>https://ninety9.dev/blog/free-shipping-threshold-calculator.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/free-shipping-threshold-calculator.html</guid>
      <pubDate>Tue, 11 Aug 2026 09:00:00 GMT</pubDate>
      <category>Shipping &amp; Fulfilment</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>The percentile method for choosing a threshold, the margin check that stops it losing money, and why per-country thresholds beat one global number.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>Set the threshold from your order value distribution, not your average. The average is already cleared by half your orders and changes nobody&#39;s behaviour.</li><li>The 60th to 75th percentile is the working band. Below it you subsidise existing behaviour, above it the goal reads as unreachable.</li><li>Run the margin check - gross profit on the incremental spend must exceed your average shipping cost, or the threshold loses money on every order it moves.</li><li>One global threshold is wrong in most markets, because both sides of the margin equation change at the border.</li></ul></section>
<p>The free shipping threshold is the most common average-order-value tactic in ecommerce and one of the most commonly mis-set. Most stores pick a round number that feels close to their <a href="/blog/how-to-increase-average-order-value-shopify.html">average order value</a>, publish it, and never revisit it.</p>
<p>That approach has a specific failure mode: a threshold set at or below your average order value is cleared by roughly half your orders without anyone changing behaviour. You have not created incremental revenue. You have given away shipping on half your orders.</p>
<p>Here is the method that avoids that.</p>
<h2 id="free-shipping-is-a-goal-not-a-discount">Free shipping is a goal, not a discount</h2>
<p>The mechanism is worth understanding because it explains why the number matters so much.</p>
<p>A shipping fee is a cost the shopper weighs against convenience. A free shipping threshold is a <em>target</em> they can hit. Those are different psychological objects. The first invites a comparison; the second invites an action.</p>
<p>The effect is well documented and reliably reproducible: shoppers who are shown a specific remaining amount to reach free shipping add items at a materially higher rate than shoppers shown the same policy stated as a rule. It works even when the fee being avoided is small, because the shopper is no longer evaluating $6.90 against convenience — they are completing a task.</p>
<p>That reframing only happens if the target is <em>reachable but not already met</em>. Which is entirely a function of where you set it.</p>
<h2 id="step-1-get-the-distribution-not-the-average">Step 1: Get the distribution, not the average</h2>
<p>Export the last 90 days of orders and their values. Sort them. Then find the percentiles.</p>
<p>A typical store might look like this:</p>
<div class="tablewrap"><table><thead><tr><th>Percentile</th><th>Order value</th></tr></thead><tbody>
<tr><td>25th</td><td>$28</td></tr>
<tr><td>50th (median)</td><td>$46</td></tr>
<tr><td>60th</td><td>$54</td></tr>
<tr><td>70th</td><td>$63</td></tr>
<tr><td>75th</td><td>$71</td></tr>
<tr><td>90th</td><td>$104</td></tr>
<tr><td>Average</td><td>$58</td></tr>
</tbody></table></div>
<p>Notice that the average ($58) sits between the 60th and 70th percentile — which is normal, because order value distributions have a long right tail that drags the mean above the median.</p>
<p>Setting the threshold at $58 means roughly 38% of orders already clear it. Those customers get free shipping for doing what they were already doing.</p>
<h2 id="step-2-pick-from-the-60th-75th-band">Step 2: Pick from the 60th–75th band</h2>
<p>Your candidate threshold sits between the 60th and 75th percentile. In the table above, that is $54 to $71.</p>
<p>The reasoning:</p>
<ul>
<li><strong>Below the 60th</strong> — too many orders already clear it. High subsidy, low behaviour change.</li>
<li><strong>Above the 75th</strong> — the gap between a typical basket and the threshold is large enough that shoppers disengage from the goal entirely. An unreachable target is not a target.</li>
<li><strong>Inside the band</strong> — a meaningful group of shoppers sits close but not there. Those are the orders you can move.</li>
</ul>
<p>Within the band, lean lower if your margin is thin or your category is price-sensitive, and higher if your margin is healthy and your products have natural complements.</p>
<h2 id="step-3-run-the-margin-check">Step 3: Run the margin check</h2>
<p>This is the step that gets skipped, and it is the one that determines whether the threshold makes money.</p>
<blockquote><p>Gross profit on the incremental spend must exceed your average shipping cost.</p></blockquote>
<p>Say you choose $65 with a median order value of $46. The average shopper you move up spends an extra $19. At a 55% gross margin, that is $10.45 of additional gross profit. If your average shipping cost is $7.20, you net $3.25 per moved order.</p>
<p>If your average shipping cost were $12, the same threshold loses $1.55 on every order it moves. You would need a threshold of at least $46 + ($12 ÷ 0.55) ≈ $68 just to break even, and higher to make anything.</p>
<aside class="callout callout--key">
<p class="callout__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 7a5 5 0 1 0-4.5 5H12v3h3v3h4v-4l-4-4V7Z"/></svg>The minimum viable threshold</p>
<p><code>threshold ≥ median order value + (average shipping cost ÷ gross margin)</code></p>
<p>Run this before anything else. If the result lands above your 75th percentile, free shipping thresholds are not the right tactic for your category — a flat reduced shipping rate usually works better.</p>
</aside>
<h2 id="step-4-round-it">Step 4: Round it</h2>
<p>$68.42 is not a goal, it is an output. $70 is a goal.</p>
<p>Round numbers work better because they are easier to hold in mind and easier to calculate against. &quot;I need $14 more&quot; is a manageable thought. &quot;I need $12.42 more&quot; is arithmetic.</p>
<p>Round up rather than down when you have the margin room. The gap in behaviour between $65 and $70 is small; the gap in your economics is not.</p>
<h2 id="step-5-split-it-by-market">Step 5: Split it by market</h2>
<p>One global threshold is wrong in most markets, because both sides of the margin equation change at the border.</p>
<p>Your shipping cost to a neighbouring country is not your domestic cost. Your order value distribution in a market where you are unknown is not the distribution in your home market. A threshold derived from domestic data and converted at the spot rate encodes neither.</p>
<p>Do the whole exercise per market for your top three or four, and use a sensible default elsewhere. And set each one as a round number in the local currency rather than converting — €70, not €68.42.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/goalify/icon.png" alt="Goalify: Free Shipping Bar PRO app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Goalify: Free Shipping Bar PRO</p>
<p class="appcta__text">Free shipping bars, free gifts, BOGO and AI upsells driven by stackable cart goals.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/goalify?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="step-6-make-it-visible">Step 6: Make it visible</h2>
<p>A threshold nobody can see their progress against is just a policy on a page.</p>
<p>The progress bar is what turns it into a goal, and the specific thing that makes it work is showing the <em>remaining amount</em> rather than the target:</p>
<ul>
<li><strong>&quot;Free shipping over $70&quot;</strong> — a rule. It is about you.</li>
<li><strong>&quot;You&#39;re $14 away from free shipping&quot;</strong> — a task. It is about them.</li>
</ul>
<p>The second consistently outperforms the first. Show it in the cart drawer first, the cart page second, and consider the product page third.</p>
<h2 id="common-mistakes">Common mistakes</h2>
<p><strong>Setting it at the average.</strong> Covered above, and it is the most common error by a distance.</p>
<p><strong>Never revisiting it.</strong> Carrier rates change. Your catalogue prices change. Your order mix changes with every campaign. A threshold set eighteen months ago is a threshold set for a different business.</p>
<p><strong>One threshold across wildly different products.</strong> If you sell both $15 accessories and $400 machines, a single threshold is meaningless for one of them. Consider a collection-scoped threshold.</p>
<p><strong>Excluding half the catalogue without saying so.</strong> A threshold that quietly does not apply to bulky items generates more support tickets than revenue. If there are exclusions, state them in the same sentence as the offer.</p>
<p><strong>Not stacking a second goal.</strong> A shopper who lands at $71 on a $70 threshold has no further reason to add anything. A second tier — an order discount or a free gift at a higher value — keeps the mechanic working across the upper half of your distribution.</p>
<aside class="callout callout--warn">
<p class="callout__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/></svg>Watch the discount interaction</p>
<p>Does your threshold measure against the pre-discount or post-discount subtotal? Post-discount is the safer default. Pre-discount means a customer with a 30% code can trigger free shipping on a basket whose real value is well below your floor.</p>
</aside>
<h2 id="measuring-whether-it-worked">Measuring whether it worked</h2>
<p>Do not use average order value on its own — a threshold mechanically raises AOV whether or not it made money.</p>
<p>Track:</p>
<ul>
<li><strong>Share of orders above the threshold</strong>, before and after. This is the behaviour change.</li>
<li><strong>Median order value</strong>, which is less distorted by outliers than the mean.</li>
<li><strong>Shipping cost as a percentage of revenue.</strong> The direct cost of the policy.</li>
<li><strong>Gross profit per order.</strong> The verdict.</li>
<li><strong>Cart-to-checkout rate.</strong> The guardrail. A threshold that is too high can suppress conversion among shoppers who conclude they will never reach it.</li>
</ul>
<p>Give it four weeks minimum. Compare against the same period rather than the previous month if your category has any seasonality at all.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>What should my free shipping threshold be?</summary><div class="faq__a"><p>Somewhere between the 60th and 75th percentile of your order value distribution over the last ninety days, subject to a margin check. There is no universal number because it depends entirely on the shape of your distribution and your fulfilment cost, both of which are specific to your store.</p></div></details>
<details><summary>Is free shipping actually worth offering?</summary><div class="faq__a"><p>For most consumer categories, yes, because shipping cost is the most commonly cited reason for cart abandonment and a threshold converts that objection into a goal. It stops being worth it when your shipping cost is high relative to your order value and margin, which is common for heavy or bulky goods.</p></div></details>
<details><summary>Should the threshold be a round number?</summary><div class="faq__a"><p>Yes. Round numbers work better as goals because they are easier to hold in mind and easier to calculate against. A threshold produced by currency conversion, such as sixty-eight euros, reads as arbitrary. Round it.</p></div></details>
<details><summary>How often should I review the threshold?</summary><div class="faq__a"><p>Quarterly at minimum, and immediately after any carrier rate change or significant catalogue price change. Both sides of the calculation drift, and a threshold set eighteen months ago is almost certainly no longer optimal.</p></div></details>
<details><summary>Does a free shipping threshold work without a progress bar?</summary><div class="faq__a"><p>Much less well. A threshold nobody can see their progress against is just a policy. The bar is what converts it into an active goal, because it shows the shopper a personal, incomplete task with a specific remaining amount.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Post-Purchase Order Editing on Shopify — The Complete Guide</title>
      <link>https://ninety9.dev/blog/shopify-order-editing-guide.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/shopify-order-editing-guide.html</guid>
      <pubDate>Sat, 08 Aug 2026 09:00:00 GMT</pubDate>
      <category>Retention &amp; Post-Purchase</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>What customers actually want to change after checkout, which edits are safe to allow, the rules you need around them, and why the edit screen is a revenue surface.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>Address changes, variant swaps and quantity changes make up the large majority of post-purchase edit requests, and all three are safe to automate before fulfilment.</li><li>Every edit handled by the customer is a support ticket that never gets written, and often a cancellation that never happens.</li><li>The edit window must close at fulfilment. Allowing changes after a label is printed converts a cheap fix into an expensive one.</li><li>The edit screen puts a warm, engaged buyer on a page you control, which makes it one of the highest-converting upsell surfaces in ecommerce.</li></ul></section>
<p>There is a window in every order between &quot;payment confirmed&quot; and &quot;parcel dispatched&quot;. For most stores it lasts somewhere between a few hours and a couple of days, and almost nothing happens in it.</p>
<p>It is, in practice, the most operationally expensive window in the whole customer journey. It is where address corrections arrive, where &quot;can I change the size&quot; emails land, where cancellations are requested, and where a customer&#39;s minor mistake turns into a return you will pay for twice.</p>
<p>It is also, if you build for it, a revenue surface.</p>
<h2 id="what-customers-actually-want-to-change">What customers actually want to change</h2>
<p>The distribution is consistent across categories:</p>
<div class="tablewrap"><table><thead><tr><th>Request</th><th>Share of post-purchase contacts</th><th>Safe to self-serve?</th></tr></thead><tbody>
<tr><td>Change shipping address</td><td>Largest single category</td><td>Yes, before fulfilment</td></tr>
<tr><td>Change size or variant</td><td>Large</td><td>Yes, same product</td></tr>
<tr><td>Change quantity</td><td>Moderate</td><td>Yes, with rules</td></tr>
<tr><td>Add an item</td><td>Moderate</td><td>Yes — and profitable</td></tr>
<tr><td>Remove an item</td><td>Moderate</td><td>With rules</td></tr>
<tr><td>Cancel entirely</td><td>Moderate</td><td>Deflect first</td></tr>
<tr><td>Change shipping method</td><td>Smaller</td><td>With rules</td></tr>
<tr><td>Update contact details</td><td>Smaller</td><td>Yes</td></tr>
</tbody></table></div>
<p>The top four are the bulk of the volume, and all four are safe to automate before fulfilment. That is the case for building this: a majority of your post-purchase support load is a form.</p>
<h2 id="why-shopify-does-not-do-this-natively">Why Shopify does not do this natively</h2>
<p>Shopify lets a merchant edit orders from the admin. It does not provide a customer-facing self-service interface for the same operations.</p>
<p>That is a defensible product decision — <a href="/apps/reviso.html">order editing</a> touches inventory, payment authorisation, fulfilment status, tax calculation and discount logic all at once, and getting it wrong creates real financial mess. But it means the gap is filled by support staff doing manual admin work, which is the most expensive possible way to handle a high-volume, low-complexity request.</p>
<aside class="callout callout--key">
<p class="callout__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 7a5 5 0 1 0-4.5 5H12v3h3v3h4v-4l-4-4V7Z"/></svg>The cost of the manual path</p>
<p>A customer emails about an address change. Support reads it, opens the admin, finds the order, checks fulfilment status, edits the address, replies. Call it six to eight minutes end to end, plus the customer waiting hours for a reply and possibly emailing again in the meantime. Multiply by the share of orders that need it.</p>
</aside>
<h2 id="edits-ranked-by-risk">Edits ranked by risk</h2>
<h3 id="low-risk-enable-by-default">Low risk — enable by default</h3>
<p><strong>Shipping address.</strong> Highest volume, lowest risk before a label is printed. The only real consideration is whether the new address changes the shipping zone or rate; if it does, either recalculate or restrict cross-zone changes.</p>
<p><strong>Variant swap within the same product.</strong> Size or colour changes at the same price are a stock movement and nothing else. Check availability of the target variant, release the original.</p>
<p><strong>Contact details.</strong> No commercial implications at all.</p>
<p><strong>Adding items.</strong> Genuinely profitable, and the reason this whole feature has an ROI beyond cost saving. Requires collecting additional payment, which Shopify supports through order editing.</p>
<h3 id="medium-risk-enable-with-rules">Medium risk — enable with rules</h3>
<p><strong>Quantity increase.</strong> Straightforward, needs a stock check and additional payment.</p>
<p><strong>Quantity decrease.</strong> Needs a rule for what happens to threshold-based rewards. If the order drops below the free shipping threshold, do you charge shipping retroactively? Usually not worth the friction — but decide it explicitly.</p>
<p><strong>Removing an item.</strong> Same threshold question, plus the bundle question: if the removed item was part of a discounted set, does the discount survive? It should not, and the recalculation needs to be visible to the customer before they confirm.</p>
<p><strong>Shipping method change.</strong> Fine if it is an upgrade the customer pays for. Downgrades after the fact are usually not worth allowing.</p>
<h3 id="high-risk-restrict-or-handle-manually">High risk — restrict or handle manually</h3>
<p><strong>Cross-product swaps.</strong> Different product entirely, different price, different tax treatment. Effectively a cancel-and-reorder, and cleaner to handle that way.</p>
<p><strong>Currency or market changes.</strong> Do not allow. The order was placed under a specific market&#39;s pricing, tax and duty configuration.</p>
<p><strong>Anything after fulfilment.</strong> The window must close.</p>
<h2 id="the-window-must-close-at-fulfilment">The window must close at fulfilment</h2>
<p>This is the single most important rule.</p>
<p>Before fulfilment, an edit is a database change. After fulfilment, it is a warehouse operation — retrieving a picked order, voiding a label, repacking, re-dispatching — or, worse, an intercept request to a carrier.</p>
<p>Closing the window automatically at fulfilment is what keeps this feature cheap. It should not be a policy that support enforces; it should be a state the interface reflects. When an order is fulfilled, the edit options simply are not there.</p>
<p>For stores with same-day dispatch this window can be very short. That is fine — a two-hour window still catches a large share of the mistakes, because most people notice a wrong address within minutes of receiving the confirmation email.</p>
<h2 id="rules-worth-having">Rules worth having</h2>
<p>Self-service does not mean unrestricted. The controls that matter:</p>
<ul>
<li><strong>By fulfilment status.</strong> The hard gate.</li>
<li><strong>By time since order.</strong> A secondary limit for stores with slow fulfilment, so an order placed three weeks ago is not still editable.</li>
<li><strong>By country.</strong> Some markets have customs or duty implications that make address changes genuinely risky.</li>
<li><strong>By customer tag.</strong> Wholesale and B2B accounts often need different rules — or no self-service at all.</li>
<li><strong>By order value.</strong> High-value orders may warrant a manual review step.</li>
<li><strong>By edit type.</strong> Allow address changes everywhere, restrict item swaps to domestic orders only, and so on.</li>
</ul>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/reviso/icon.png" alt="Reviso: Order editing &amp; Upsell app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Reviso: Order editing &amp; Upsell</p>
<p class="appcta__text">Post-purchase self-service order editing with cancellation deflection and AI upsells.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/reviso-order-editor?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="the-edit-screen-as-a-revenue-surface">The edit screen as a revenue surface</h2>
<p>Here is the part that turns a cost-saving feature into a growth one.</p>
<p>Think about who is on that page. Someone who has already paid. Someone who is engaged enough to have come back and taken a deliberate action. Someone whose order has not shipped, which means adding an item costs you nothing extra in fulfilment — it goes in the same box.</p>
<p>That is a better prospect than almost any visitor on your storefront, and they are on a page you fully control.</p>
<p>What works there:</p>
<ul>
<li><strong>&quot;Forgot something?&quot;</strong> with recommendations based on what they ordered. The framing is helpful rather than salesy, and it is true.</li>
<li><strong>Recently viewed items</strong> from before the purchase. They looked and did not buy; this is a second chance with zero friction.</li>
<li><strong>Free shipping top-up</strong>, if the order is below a threshold and they are now adding items anyway.</li>
<li><strong>Replenishment suggestions</strong> for consumables they have bought before.</li>
</ul>
<p>The conversion rates on this surface are unusually high, for the simple reason that every friction point normally standing between a shopper and an additional purchase has already been cleared.</p>
<aside class="callout callout--warn">
<p class="callout__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/></svg>Do not gate the edit behind the offer</p>
<p>The upsell must be secondary to the task. A customer who came to fix their address and cannot find the address field because the page leads with product recommendations will write to support anyway - and you will have paid for the feature without getting the benefit.</p>
</aside>
<h2 id="cancellation-deflection">Cancellation deflection</h2>
<p>A specific and valuable case. When a customer starts a cancellation, you have one interaction to understand why and offer an alternative.</p>
<p>The sequence that works:</p>
<ol>
<li><strong>Ask the reason.</strong> A short list — changed my mind, wrong item, found it cheaper, taking too long, ordered by mistake.</li>
<li><strong>Respond to the reason.</strong> Wrong item → offer the edit flow instead. Taking too long → show the actual delivery estimate. Changed my mind → offer an incentive.</li>
<li><strong>Make cancelling easy if they still want to.</strong> A deflection flow that traps people produces chargebacks and reviews, which cost more than the order.</li>
</ol>
<p>A saved order is worth substantially more than a refunded one, and a reason code is worth having regardless of the outcome. Over a few months, the reason distribution is one of the most useful datasets in the business.</p>
<h2 id="rollout">Rollout</h2>
<ol>
<li><strong>Address changes only</strong>, before fulfilment. Measure the drop in support contacts.</li>
<li><strong>Add variant swaps</strong> within the same product.</li>
<li><strong>Add quantity changes</strong> with a threshold rule.</li>
<li><strong>Add the item-adding flow</strong> with recommendations. This is where revenue starts.</li>
<li><strong>Add cancellation deflection</strong> with reason capture.</li>
<li><strong>Layer in rules</strong> by country, tag and order value once the base flows are stable.</li>
</ol>
<p>Do not build all six at once. The first step alone typically removes a large fraction of post-purchase support volume, and it is the one with essentially no downside.</p>
<h2 id="measuring-it">Measuring it</h2>
<ul>
<li><strong>Post-purchase support contacts per 100 orders</strong>, before and after. The primary cost metric.</li>
<li><strong>Self-service edit rate</strong> — orders edited by the customer ÷ orders eligible.</li>
<li><strong>Cancellation rate</strong>, and the deflection rate within it.</li>
<li><strong>Return rate for avoidable reasons</strong> — wrong size, wrong item, wrong address. This is the slow one; expect it to move over a quarter rather than a month.</li>
<li><strong>Revenue per edit session.</strong> The upside metric.</li>
<li><strong>Fulfilment exceptions caused by edits.</strong> The guardrail. If warehouse errors rise after launch, the window is closing too late.</li>
</ul>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Can customers edit their Shopify order after checkout?</summary><div class="faq__a"><p>Not natively in a self-service way. Shopify lets merchants edit orders from the admin, but there is no built-in customer-facing interface for changing an address, swapping a variant or adjusting quantities. That gap is why most stores handle these requests manually through support.</p></div></details>
<details><summary>What order edits are safe to allow?</summary><div class="faq__a"><p>Shipping address, item quantity, variant swap within the same product and adding items are all safe before fulfilment because none of them change the fundamental commercial terms. Removing items and changing shipping method need rules attached because both can affect thresholds and margin.</p></div></details>
<details><summary>When should the edit window close?</summary><div class="faq__a"><p>At fulfilment. Once a label is printed or an item is picked, an edit stops being a database change and becomes a warehouse operation. Closing the window automatically at that point is what keeps the feature cheap to run.</p></div></details>
<details><summary>Does order editing reduce support tickets?</summary><div class="faq__a"><p>Substantially. Address changes and wrong-variant corrections are among the highest-volume post-purchase contacts for most stores, and both are entirely self-serviceable. Stores that enable self-service editing typically see a marked drop in post-purchase contact volume within weeks.</p></div></details>
<details><summary>Can order editing reduce returns?</summary><div class="faq__a"><p>Yes, for the category of returns that begin as a mistake at checkout rather than a problem with the product. A customer who ordered the wrong size and can fix it before dispatch never generates a return, which removes the outbound shipping, the return shipping, the restocking and the refund processing.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>The Shopify Cart Drawer Guide — Design, Speed and Upsells That Convert</title>
      <link>https://ninety9.dev/blog/shopify-cart-drawer-complete-guide.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/shopify-cart-drawer-complete-guide.html</guid>
      <pubDate>Tue, 04 Aug 2026 09:00:00 GMT</pubDate>
      <category>Cart &amp; Checkout</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>What separates a cart drawer that sells from one that just lists items — layout order, the six blocks that matter, performance rules and the upsells worth adding.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>A slide cart drawer beats a cart page because it keeps the shopper on the product they were looking at, removing a full navigation step from the funnel.</li><li>Block order matters more than block count. Progress bar, line items, upsell, delivery estimate, trust, checkout — in that order.</li><li>Two upsell offers is the ceiling. A third reliably reduces the take rate of the first two.</li><li>Load the drawer asynchronously and render it only on open, so it never touches your Largest Contentful Paint.</li></ul></section>
<p>Most Shopify themes ship with a cart drawer that does exactly one thing: it lists what the shopper added. It is a receipt with a checkout button attached.</p>
<p>That is a strange design decision when you consider who is looking at it. Someone who has opened their cart has already decided to buy. They are past every objection that costs you conversions on the product page. They are, in that moment, the most valuable visitor on your site — and you are showing them a list.</p>
<p>This guide covers what to put in that space instead, in what order, and where the limits are.</p>
<h2 id="why-a-drawer-beats-a-cart-page">Why a drawer beats a cart page</h2>
<p>The argument for a slide-out drawer over a dedicated <code>/cart</code> page comes down to a single word: continuity.</p>
<p>When a shopper clicks &quot;add to cart&quot; on a product page and the site navigates them to a cart page, three things happen. They lose the product they were looking at. They lose their scroll position in the collection. And they land on a page whose only forward action is &quot;checkout&quot; — which means the natural next move is to leave your catalogue entirely.</p>
<p>A drawer keeps all of that context. The product page is still behind the overlay. Closing the drawer returns them exactly where they were. Adding a second item costs one click instead of a full navigation cycle.</p>
<p>The exception worth naming: if your carts routinely run to fifteen line items, or your products need per-item configuration, or you sell B2B quantities, a full cart page has room a drawer does not. Everyone else should be using a drawer.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/addy/icon.png" alt="Addy: AI Cart Drawer &amp; Bundles app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Addy: AI Cart Drawer &amp; Bundles</p>
<p class="appcta__text">AI cart drawer with in-cart upsells, volume bundles, free shipping bars and per-Market targeting.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/addy-cart-drawer?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="the-six-blocks-that-matter-in-order">The six blocks that matter, in order</h2>
<p>The order is not cosmetic. Each block sets up the one below it.</p>
<h3 id="1-the-progress-bar-at-the-very-top">1. The progress bar, at the very top</h3>
<p>The first thing a shopper should see is not what they have — it is what they are close to getting. A progress bar showing &quot;$18 away from free shipping&quot; reframes the entire drawer from a summary into a task.</p>
<p>Put it above the line items. Below them it reads as a footnote; above them it reads as an instruction.</p>
<h3 id="2-line-items-with-variant-clarity">2. Line items with variant clarity</h3>
<p>Image, title, variant, quantity stepper, price, remove. The failure mode here is ambiguity about which variant is in the cart. &quot;Blue / Large&quot; needs to be visible without hovering, because a shopper who is unsure will open a new tab to check, and that tab is where sessions go to die.</p>
<p>Quantity should be a stepper, not a text input. Steppers get used; text inputs get ignored.</p>
<h3 id="3-one-upsell-block">3. One upsell block</h3>
<p>Not a carousel of twelve. One block, showing one or two products, chosen from what is already in the cart.</p>
<p>The single biggest determinant of whether this converts is relevance, and the only reliable source of relevance is your own order history. Category-based suggestions produce the classic failure — recommending a second pair of the same shoes to someone who just added shoes.</p>
<h3 id="4-the-delivery-estimate">4. The delivery estimate</h3>
<p>&quot;Arrives 22–24 August&quot; removes the largest remaining unknown before checkout. It costs you nothing, it is trivially true if your fulfilment is predictable, and it consistently reduces the number of shoppers who close the drawer to go and look for your shipping policy page.</p>
<h3 id="5-trust-and-reassurance">5. Trust and reassurance</h3>
<p>Returns window, secure payment, support availability. Small, quiet, below the fold of the drawer. This block exists to answer objections, not to shout.</p>
<h3 id="6-checkout-sticky">6. Checkout, sticky</h3>
<p>The checkout button should be pinned to the bottom of the drawer and visible regardless of scroll position. Show the subtotal on or immediately above it. If you accept express checkout methods, they belong here too — but below the main button, not above it, because express buttons that dominate the drawer cannibalise the shoppers who would have added one more item.</p>
<aside class="callout callout--tip">
<p class="callout__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18h6M10 22h4M12 2a7 7 0 0 0-4 12.7V17h8v-2.3A7 7 0 0 0 12 2Z"/></svg>A useful test</p>
<p>Screenshot your cart drawer and remove the product images. If you cannot tell within two seconds what the shopper should do next, the hierarchy is wrong.</p>
</aside>
<h2 id="upsells-that-work-in-a-cart-drawer">Upsells that work in a cart drawer</h2>
<p>Not every offer type belongs here. The cart is a <em>confirmation</em> surface, and offers that ask the shopper to reconsider their choice will kill momentum.</p>
<div class="tablewrap"><table><thead><tr><th>Offer type</th><th>Works in cart?</th><th>Why</th></tr></thead><tbody>
<tr><td>Complementary accessory</td><td>Yes</td><td>Additive, cheap relative to the anchor, no reconsideration required</td></tr>
<tr><td>Quantity break on an item already in cart</td><td>Yes</td><td>Pure upgrade, zero new decisions</td></tr>
<tr><td>Free gift threshold</td><td>Yes</td><td>Reward framing, no cost to the shopper</td></tr>
<tr><td>Shipping protection add-on</td><td>Yes, if unticked</td><td>Genuinely useful, near-zero COGS</td></tr>
<tr><td>Alternative to a cart item</td><td>No</td><td>Forces reconsideration of a decision already made</td></tr>
<tr><td>Unrelated bestseller</td><td>No</td><td>Reads as noise, dilutes the relevant offer</td></tr>
<tr><td>Email capture</td><td>No</td><td>Wrong moment entirely; you are about to get their email at checkout</td></tr>
</tbody></table></div>
<p>The price relationship matters. An accessory priced at 15–40% of the anchor product converts well. At 80% of the anchor it reads as a second purchase decision, and the shopper defers it.</p>
<h2 id="personalising-by-market-and-segment">Personalising by market and segment</h2>
<p>A single cart drawer for every shopper is a compromise you no longer have to make.</p>
<p>The obvious axis is geography. A free shipping threshold of $75 is aggressive in one market and trivially easy in another. A delivery estimate of &quot;2–3 days&quot; is a lie outside your domestic zone. Both should change with Shopify Markets.</p>
<p>The less obvious axis is customer segment. A first-time visitor and a customer with four previous orders should not see the same cart:</p>
<ul>
<li><strong>First-timers</strong> benefit from trust blocks, returns policy and a lower-friction first goal.</li>
<li><strong>Repeat customers</strong> already trust you. Give that space to a higher reward tier or a replenishment suggestion instead.</li>
<li><strong>Wholesale or tagged accounts</strong> should not see consumer discount mechanics at all.</li>
</ul>
<p>The most granular version of this is block-level targeting — the drawer stays the same but individual blocks appear or hide based on conditions. That is usually more maintainable than building five separate carts.</p>
<h2 id="performance-rules">Performance rules</h2>
<p>A cart drawer that costs you 300ms of Largest Contentful Paint has to earn that back before it breaks even, and it usually does not.</p>
<p>Three rules keep it honest:</p>
<ol>
<li><strong>Render on open, not on load.</strong> The drawer markup does not need to exist until the shopper asks for it. Nothing in a closed drawer should be in the critical rendering path.</li>
<li><strong>Install as a theme app extension.</strong> No <code>theme.liquid</code> injection. This is also what makes uninstalling clean — no orphaned script tags left behind in six months.</li>
<li><strong>Measure before and after.</strong> Run PageSpeed Insights on a product page before install and after. If LCP moved by more than a rounding error, something is loading that should not be.</li>
</ol>
<aside class="callout callout--warn">
<p class="callout__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/></svg>The hidden cost of a &quot;free&quot; drawer</p>
<p>Several popular cart apps load their entire configuration payload — every block, every rule, every translation — on first page load rather than on drawer open. It works fine on your laptop and badly on a mid-range Android phone on 4G, which is most of your traffic.</p>
</aside>
<h2 id="a-realistic-setup-order">A realistic setup order</h2>
<p>If you are building this from scratch, do it in this sequence and measure between each step:</p>
<ol>
<li>Drawer with clean line items and a sticky checkout button. Baseline.</li>
<li>Add the free shipping progress bar with a threshold set from your actual order distribution.</li>
<li>Add the delivery estimate.</li>
<li>Add one upsell block with pairs drawn from real order history.</li>
<li>Add trust blocks and reassurance copy.</li>
<li>Only then: market targeting, segment rules and a second reward tier.</li>
</ol>
<p>Steps two and four are where most of the money is. Steps one and three are where most of the conversion-rate protection is.</p>
<h2 id="what-to-measure">What to measure</h2>
<p>Cart-level metrics are more useful than store-level ones here, because they isolate the surface you changed:</p>
<ul>
<li><strong>Cart-to-checkout rate</strong> — did the drawer make it easier or harder to proceed?</li>
<li><strong>Units per order</strong> — did the upsell block actually add items?</li>
<li><strong><a href="/blog/how-to-increase-average-order-value-shopify.html">Average order value</a></strong> — the headline, but not on its own.</li>
<li><strong>Gross profit per session</strong> — the honest number, after discounts and shipping cost.</li>
<li><strong>Upsell take rate</strong> — offers accepted ÷ offers shown, per block.</li>
</ul>
<p>If cart-to-checkout drops while AOV rises, you have added friction and traded conversions for basket size. Sometimes that trade is profitable. Check, do not assume.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Is a cart drawer better than a cart page?</summary><div class="faq__a"><p>For almost all stores, yes. A drawer keeps the shopper on the page they were already engaged with, so adding one more item does not require a round trip. The exception is high-consideration carts with many line items, configuration options or B2B quantities, where a full page has room the drawer does not.</p></div></details>
<details><summary>Will a cart drawer app slow my store down?</summary><div class="faq__a"><p>It should not. A well-built drawer loads asynchronously, renders only when opened, and never blocks the initial paint. Check that any app you install ships as a theme app extension rather than injecting a script into theme.liquid, and confirm your Largest Contentful Paint is unchanged before and after install.</p></div></details>
<details><summary>How many upsells should a cart drawer show?</summary><div class="faq__a"><p>One or two. Every additional offer dilutes attention and lowers the take rate of the others. If you have more than two things worth suggesting, that is a signal your recommendation logic needs to be better, not that you need more slots.</p></div></details>
<details><summary>Should the cart drawer open automatically after add to cart?</summary><div class="faq__a"><p>Yes, in most cases. Auto-opening confirms the action, removes any doubt that the item was added, and puts your progress bar and upsells in front of a shopper at their highest point of intent. The exception is stores where shoppers routinely add many items in a row from a collection page.</p></div></details>
<details><summary>Do I need to edit my theme to add a cart drawer?</summary><div class="faq__a"><p>Not with a modern app. Theme app extensions install as blocks in the theme editor with no code changes, which means the drawer can be enabled and disabled from the admin and uninstalls cleanly with nothing left behind in your Liquid files.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Exit-Intent Popups on Shopify — The Honest Guide to What Actually Works</title>
      <link>https://ninety9.dev/blog/exit-intent-popups-shopify.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/exit-intent-popups-shopify.html</guid>
      <pubDate>Tue, 28 Jul 2026 09:00:00 GMT</pubDate>
      <category>CRO &amp; Analytics</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>How exit detection really works on desktop and mobile, which offers convert on the way out, and why an exit popup is the only popup that cannot cost you a conversion.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>Exit intent is the only popup trigger with no conversion downside, because the visitor has already signalled they are leaving.</li><li>Desktop detection uses cursor velocity toward the browser chrome. Mobile has no equivalent, so mobile exit popups rely on back-navigation and scroll signals and are inherently less precise.</li><li>Where the popup fires matters more than what it says. On a cart page it should address the reason for leaving. On a collection page it is just an interruption.</li><li>One offer, one action. Exit popups with two competing choices convert measurably worse than those with one.</li></ul></section>
<p>Popups have earned their reputation. The five-second timer that covers a page you have just started reading is a genuinely bad piece of interface design and everyone involved knows it.</p>
<p>Exit intent is a different thing, and the difference is structural rather than cosmetic. A timed popup interrupts someone who is still deciding. An exit popup appears in front of someone who has already decided to leave. There is no conversion left to lose.</p>
<p>That single property is why exit intent is worth doing properly while most other popup triggers are worth deleting.</p>
<h2 id="how-exit-detection-actually-works">How exit detection actually works</h2>
<p>It is worth understanding the mechanism, because the mechanism explains the limits.</p>
<h3 id="desktop">Desktop</h3>
<p>The browser exposes cursor coordinates continuously. Exit-intent code tracks position over time and looks for a specific pattern: rapid upward movement toward the top edge of the viewport, where the address bar, tab strip, back button and close button all live.</p>
<p>When velocity toward that edge crosses a threshold, the code fires. Typical implementations also require the pointer to actually leave the viewport bounds, which reduces false positives from someone reaching for a bookmark.</p>
<p>It is a heuristic, not a certainty. Someone reaching for their coffee will occasionally trigger it. That is acceptable, because the cost of a false positive is low and the popup is dismissible.</p>
<h3 id="mobile">Mobile</h3>
<p>There is no cursor, so there is no equivalent signal. Mobile implementations fall back on:</p>
<ul>
<li><strong>Back-navigation intent</strong> — intercepting the history back gesture. The strongest available mobile signal.</li>
<li><strong>Rapid upward scroll</strong> — weak, because it also describes someone scanning a page.</li>
<li><strong>Inactivity</strong> — weakest. Someone who stopped scrolling might be reading.</li>
</ul>
<p>The honest position: mobile exit intent is a much blunter instrument. Back-navigation is usable; the other two produce enough false positives that they stop being exit intent and become interruptions with extra steps.</p>
<aside class="callout callout--warn">
<p class="callout__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/></svg>Be careful with back-button interception</p>
<p>Hijacking the back gesture is intrusive and can trap users, which some browsers actively penalise. If you use it, the popup must be immediately and obviously dismissible, and a second back press must always leave.</p>
</aside>
<h2 id="placement-decides-the-offer">Placement decides the offer</h2>
<p>The most common mistake is running one exit popup site-wide. The visitor&#39;s location tells you why they are leaving, and the offer should answer that reason.</p>
<div class="tablewrap"><table><thead><tr><th>Page</th><th>Likely reason for leaving</th><th>Offer that fits</th></tr></thead><tbody>
<tr><td>Cart</td><td>Shipping cost, total, delivery uncertainty</td><td>Free shipping threshold reminder, delivery estimate, small discount</td></tr>
<tr><td>Checkout start</td><td>Price, payment friction, second thoughts</td><td>Reassurance, payment options, support access</td></tr>
<tr><td>Product page</td><td>Not convinced, comparison shopping</td><td>Related product, size guide, reviews, bundle</td></tr>
<tr><td>Collection page</td><td>Browsing, no intent yet</td><td>Usually nothing. Do not fire here</td></tr>
<tr><td>Blog or content</td><td>Reading, not shopping</td><td>Content offer at most</td></tr>
</tbody></table></div>
<p>The last two rows matter. A visitor leaving a collection page after twenty seconds is not abandoning a purchase — they are browsing. Firing a discount at them costs margin and gains nothing, and it is the behaviour that gave popups their reputation.</p>
<h2 id="offers-ranked-by-what-they-cost-you">Offers ranked by what they cost you</h2>
<p>Discounts are the default and the most expensive option. Try the cheaper ones first.</p>
<h3 id="1-the-threshold-reminder-free">1. The threshold reminder (free)</h3>
<p>&quot;You&#39;re $12 from free shipping.&quot; If the visitor is leaving a cart that is close to a threshold, telling them so is often enough. Costs nothing and addresses one of the most common abandonment reasons directly.</p>
<h3 id="2-the-delivery-reassurance-free">2. The delivery reassurance (free)</h3>
<p>&quot;Order today, arrives Tuesday 26 August.&quot; Answers the question they may have been leaving to go and find.</p>
<h3 id="3-the-cart-save-free">3. The cart save (free)</h3>
<p>&quot;Save your cart — we&#39;ll email you a link.&quot; Converts an abandonment into a re-engagement opportunity, and captures an email address as a side effect rather than as the ask.</p>
<h3 id="4-the-related-recommendation-free">4. The related recommendation (free)</h3>
<p>Particularly effective on product pages. The visitor may be leaving because this specific item is not right, not because your store is wrong.</p>
<h3 id="5-the-bundle-offer-costs-the-bundle-discount">5. The bundle offer (costs the bundle discount)</h3>
<p>&quot;Add the matching item and save 15%.&quot; Gives a reason to stay that is about value rather than about price.</p>
<h3 id="6-the-discount-costs-the-discount">6. The discount (costs the discount)</h3>
<p>Effective, and the one to use last. Reserve it for high-value carts or first-time visitors where the lifetime value case is strong.</p>
<aside class="callout callout--key">
<p class="callout__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 7a5 5 0 1 0-4.5 5H12v3h3v3h4v-4l-4-4V7Z"/></svg>The habituation problem</p>
<p>If you always offer a discount at exit, returning customers learn to trigger it deliberately. Within a few months you have not built a retention tactic, you have published a permanent price reduction with an extra step. Vary the offer, cap the frequency, and segment by customer history.</p>
</aside>
<h2 id="design-rules">Design rules</h2>
<p>Exit popups have a short attention window. Every element must earn its place.</p>
<ul>
<li><strong>One offer, one action.</strong> Two competing choices measurably reduce conversion. The secondary action should be &quot;no thanks&quot;, not a second offer.</li>
<li><strong>A visible, generously sized close control.</strong> A tiny grey X in a corner is a dark pattern and it will end up in a review.</li>
<li><strong>No second popup.</strong> If they dismiss it, they leave. Accept that.</li>
<li><strong>Frequency capping.</strong> Once per session, and no more than once per visitor per week.</li>
<li><strong>Suppress for converters.</strong> A customer who has purchased in this session should never see an exit offer.</li>
<li><strong>Keep it fast.</strong> The popup must render immediately. A slow popup fires after the visitor has already gone.</li>
<li><strong>Make it accessible.</strong> Focus trap while open, <code>Escape</code> closes it, focus returns to where it was. This is not optional.</li>
</ul>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/monet/icon.png" alt="Monet • AI Popup Bundle Addons app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Monet • AI Popup Bundle Addons</p>
<p class="appcta__text">AI upsell popups triggered on add to cart, checkout initiation and exit intent.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/monet?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="the-legal-side">The legal side</h2>
<p>Exit popups intersect with a few rules that are worth knowing before you launch.</p>
<ul>
<li><strong>Email capture requires consent handling.</strong> If you collect an address, GDPR applies: clear purpose, no pre-ticked boxes, and a real unsubscribe.</li>
<li><strong>Discount terms must be honest.</strong> A code that expires in ten minutes must actually expire in ten minutes. A countdown that resets on refresh is a false statement about your commercial terms.</li>
<li><strong>Accessibility is a legal requirement</strong> in a growing number of jurisdictions. A modal that cannot be dismissed with a keyboard is an accessibility failure with legal exposure attached.</li>
</ul>
<h2 id="measuring-properly">Measuring properly</h2>
<p>Exit popups have a measurement trap: the denominator is not your traffic, it is your <em>exiting</em> traffic. Judging them against site-wide conversion makes them look worse than they are.</p>
<p>Track:</p>
<ul>
<li><strong>Fire rate</strong> — popups shown ÷ sessions. Unusually high suggests false positives.</li>
<li><strong>Engagement rate</strong> — interactions ÷ shown.</li>
<li><strong>Recovery rate</strong> — sessions that converted after seeing the popup ÷ sessions shown. The headline number.</li>
<li><strong>Discount cost per recovered order</strong>, if you are offering one.</li>
<li><strong>Repeat trigger rate</strong> — the share of visitors seeing it more than once. Rising means habituation is starting.</li>
<li><strong>Return visit rate</strong> for shoppers who saw and dismissed it. The brand-damage guardrail. If people who see your exit popup come back less often than those who do not, the popup is costing more than it earns.</li>
</ul>
<p>That last metric is the one nobody measures and the one that decides whether the tactic is sustainable.</p>
<h2 id="a-sensible-starting-configuration">A sensible starting configuration</h2>
<ul>
<li>Cart page and checkout-start only. Nothing on collection or blog pages.</li>
<li>Desktop cursor-velocity trigger; mobile back-navigation only.</li>
<li>First offer: a free shipping gap reminder or a delivery estimate — no discount.</li>
<li>Frequency cap: once per session, once per week per visitor.</li>
<li>Suppressed entirely for anyone who has already purchased in the session.</li>
</ul>
<p>Run that for a month. Only add a discount tier if the free offers are not recovering enough, and only for carts above a value where the discount is clearly worth paying.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>How does exit-intent detection work?</summary><div class="faq__a"><p>On desktop it tracks cursor position and velocity, firing when the pointer moves rapidly toward the top edge of the viewport where the address bar and tab controls sit. On mobile there is no cursor, so implementations fall back on back-button navigation, rapid upward scrolling or a period of inactivity, all of which are weaker signals.</p></div></details>
<details><summary>Do exit-intent popups hurt conversion rate?</summary><div class="faq__a"><p>They are the one popup type that structurally cannot, because they only fire on visitors who have already signalled departure. The risk is not conversion loss but brand damage from a poorly targeted or aggressive popup, and from firing repeatedly at the same visitor.</p></div></details>
<details><summary>What offer works best in an exit popup?</summary><div class="faq__a"><p>It depends on where the visitor is. On a cart page, address the likely reason for leaving - shipping cost, delivery uncertainty or price. On a product page, a related recommendation or a reminder often outperforms a discount. A discount is the most expensive option and should not be the default.</p></div></details>
<details><summary>Should exit popups offer a discount every time?</summary><div class="faq__a"><p>No. Habitual discounting at exit teaches returning visitors to trigger the popup deliberately, which converts a retention tactic into a permanent margin reduction. Reserve discounts for high-value carts or first-time visitors, and use non-discount offers elsewhere.</p></div></details>
<details><summary>Do exit-intent popups work on mobile?</summary><div class="faq__a"><p>Less reliably, because the signals are weaker. Back-navigation intent is the most usable trigger. Scroll-based and inactivity-based triggers produce more false positives, which means interrupting people who were not leaving - exactly the failure mode exit intent is supposed to avoid.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Partial Refunds vs Order Edits — Fix the Shopify Order Instead of Unwinding It</title>
      <link>https://ninety9.dev/blog/partial-refunds-vs-order-edits.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/partial-refunds-vs-order-edits.html</guid>
      <pubDate>Sat, 25 Jul 2026 09:00:00 GMT</pubDate>
      <category>Retention &amp; Post-Purchase</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>When to let a customer add, swap or remove a line after checkout, and when a partial refund is the cleaner (and cheaper) path.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>If fulfilment has not started, prefer an edit (add, swap, remove) over a refund-and-reorder. Inventory and payment stay on one order.</li><li>If the box has shipped, a partial refund or a return is honest. Editing a shipped order is how you get inventory desync.</li><li>Removing a line before pick should restock and adjust payment. A &quot;refund&quot; that leaves the line on the order is a bookkeeping mess.</li><li>Document which path your team uses. Mixed practice (some agents refund, some edit) is how customers learn to ask for both.</li></ul></section>
<p>A partial refund is an unwind. An order edit is a correction. Customers ask for both using the same words (&quot;I need to change my order&quot;). Your job is to pick the path that matches whether the box still exists.</p>
<h2 id="before-fulfilment-edit">Before fulfilment: edit</h2>
<p>They paid for A and meant B. Swap the line, adjust the payment, keep the order ID. They paid for one and want two. Add the line, capture the difference, one shipment.</p>
<p>This is cheaper than refunding and asking them to reorder: they might not reorder, and you have created a second chance to lose them at checkout.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/reviso/icon.png" alt="Reviso: Order editing &amp; Upsell app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Reviso: Order editing &amp; Upsell</p>
<p class="appcta__text">Post-purchase self-service order editing with cancellation deflection and AI upsells.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/reviso-order-editor?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="after-fulfilment-refund-or-return">After fulfilment: refund or return</h2>
<p>The inventory is in a van. You cannot &quot;edit&quot; it without intercepting a carrier, which you will not do for a $12 SKU. Offer a return, a partial refund if the item is missing from the box, or a new order for what they now want.</p>
<p>Language matters. Do not call that a cancel. Do not call it an edit. Call it what the warehouse can do.</p>
<h2 id="removal-vs-refund">Removal vs refund</h2>
<p>If they drop a line before pick, restock it and adjust the capture. A refund issued while the line stays on the order will ship unless someone remembers. That is how you pay to send a thing you already refunded.</p>
<h2 id="one-playbook-for-the-team">One playbook for the team</h2>
<p>Write three rows: window open / in fulfilment / shipped. For each, the allowed action. If live chat and the self-serve portal disagree, customers will use both and you will double-process.</p>
<p>Fixing an order is almost always better than unwinding it. The skill is knowing when the order is still there to fix.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Is an order edit better than a partial refund?</summary><div class="faq__a"><p>Before fulfilment, usually yes — one order, one payment adjustment, one shipment. After fulfilment, a partial refund or return matches reality.</p></div></details>
<details><summary>What if they want a cheaper variant?</summary><div class="faq__a"><p>Swap the line if stock exists and the window is open; capture or refund the difference. Do not leave both variants on the order and refund by hand.</p></div></details>
<details><summary>Can they add a product after paying?</summary><div class="faq__a"><p>Yes, if you can charge the difference and the warehouse can still add it to the same shipment. If the order is already picked, send a second order instead of pretending it is one box.</p></div></details>
<details><summary>Do refunds hurt more than edits in analytics?</summary><div class="faq__a"><p>Refunds show up as returns and can distort merchandising. Edits keep the sale. That is a reporting reason as well as an ops reason to edit while you still can.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Put the Order-Edit Link in the Shopify Confirmation Email</title>
      <link>https://ninety9.dev/blog/post-purchase-email-edit-link.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/post-purchase-email-edit-link.html</guid>
      <pubDate>Fri, 24 Jul 2026 09:00:00 GMT</pubDate>
      <category>Retention &amp; Post-Purchase</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>Where to place a self-serve edit link after checkout, the copy that gets used, and the mistakes that send people to support anyway.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>The confirmation email is the highest-intent post-purchase surface you already send. If the edit link is not there, self-serve will under-used.</li><li>One button: &quot;Change address or items&quot;. Two links (&quot;address&quot; vs &quot;add a product&quot;) split a simple job.</li><li>Repeat the link on the order status page. Email is not opened by everyone; the status page is bookmarked by some.</li><li>Disable or rewrite the button after the cut-off. A live link that errors is worse than no link.</li></ul></section>
<p>You built a portal. Nobody opened it. That is usually because the only door was a sentence in a help centre article.</p>
<p>The order confirmation is already in the inbox, already about this order, already trusted. That is where the door goes.</p>
<h2 id="one-button-one-job">One button, one job</h2>
<p>&quot;Need to change your address, add an item, or update the delivery note? Change this order.&quot; One URL. The portal can still have separate screens inside. The email should not look like a sitemap.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/reviso/icon.png" alt="Reviso: Order editing &amp; Upsell app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Reviso: Order editing &amp; Upsell</p>
<p class="appcta__text">Post-purchase self-service order editing with cancellation deflection and AI upsells.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/reviso-order-editor?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<p>Place it above the fold of the email, after the order summary, before the footer legal. People scan. A link in the footer next to &quot;unsubscribe&quot; is not a feature.</p>
<h2 id="order-status-page-too">Order status page too</h2>
<p>Not everyone opens email on the device they will use to type a new address. The Shopify order status page is the other place they already go. Same link, same cut-off behaviour.</p>
<h2 id="after-cut-off">After cut-off</h2>
<p>Swap the button for a line: &quot;We&#39;ve started packing this order, so the details are locked. Questions? Reply to this email.&quot; A 500 from an expired token is an engineering bug that presents as a broken store.</p>
<h2 id="guest-checkout">Guest checkout</h2>
<p>If the portal requires a login and they bought as a guest, you have created a ticket. Use the same customer-access token model Shopify uses for order status. Anything else is friction you will pay for in chat.</p>
<p>Transactional email is not a newsletter. Put the operational link in it and get out of the way.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Can I add this without a developer?</summary><div class="faq__a"><p>If your edit app provides a customer link per order, you can usually drop it into the notification template. Test with a real order, not the Shopify preview dummy, because the URL is order-specific.</p></div></details>
<details><summary>Won&#39;t this increase edits?</summary><div class="faq__a"><p>Yes. That is the point. Edits in a portal are cheaper than edits in a ticket. If volume scares you, tighten the rules, do not hide the link.</p></div></details>
<details><summary>Should SMS get the same link?</summary><div class="faq__a"><p>If you send shipping SMS, a short &quot;Need to change something? [link]&quot; is useful. Do not add a second marketing SMS just for the portal.</p></div></details>
<details><summary>What about customers who checkout as guest?</summary><div class="faq__a"><p>The link must work with the order-status token Shopify already emails them. Requiring an account for a guest order sends them to support.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>How to Write a Shopify Cancellation Policy Customers Will Use Instead of Chargebacks</title>
      <link>https://ninety9.dev/blog/writing-a-cancellation-policy.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/writing-a-cancellation-policy.html</guid>
      <pubDate>Thu, 23 Jul 2026 09:00:00 GMT</pubDate>
      <category>Retention &amp; Post-Purchase</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>Clear cut-offs, what happens to payment, and the in-flow offer that turns a cancel into a keep — without making people feel trapped.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>A cancellation policy has to match operations. &quot;Cancel anytime&quot; on a made-to-order SKU is a chargeback waiting for a lawyer.</li><li>Put the policy in the order confirmation and in the cancel flow, not only in a footer page nobody reads.</li><li>Self-serve cancel before fulfilment, with an optional keep-offer (discount, gift, delay) — never a maze of &quot;are you sure&quot; screens.</li><li>After fulfilment, the honest path is return or partial refund, not a pretend cancel.</li></ul></section>
<p>People cancel for boring reasons: ordered two by accident, found it cheaper, the gift is no longer needed. A policy that cannot handle boring reasons pushes those people into the card issuer. Chargebacks cost more than a cancelled order.</p>
<p>Write the policy for the warehouse you have, then put it where the click happens.</p>
<h2 id="match-the-words-to-the-box">Match the words to the box</h2>
<p>If you can stop the order before it is picked, say &quot;You can cancel until we start packing.&quot; If you cannot, say &quot;Once we ship, use returns.&quot; Do not say &quot;cancel anytime&quot; unless you mean it for every SKU, including custom print.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/reviso/icon.png" alt="Reviso: Order editing &amp; Upsell app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Reviso: Order editing &amp; Upsell</p>
<p class="appcta__text">Post-purchase self-service order editing with cancellation deflection and AI upsells.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/reviso-order-editor?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="put-it-in-the-flow">Put it in the flow</h2>
<p>Footer legal pages are for lawyers. The customer needs one paragraph in the confirmation email and the same paragraph at the top of the cancel screen. If the portal and the policy page disagree, the portal wins in their memory and you lose the dispute.</p>
<h2 id="the-keep-offer">The keep offer</h2>
<p>When they start a cancel, you may offer a reason to keep the order: a small discount, a free add-on, a delayed delivery date. One offer, easy decline, then cancel completes.</p>
<p>Two extra screens, a survey, and a buried confirm are how you get the 1-star review that says you would not let them leave.</p>
<h2 id="after-it-has-shipped">After it has shipped</h2>
<p>The button should not say Cancel. It should say Start a return, or Contact us, depending on your setup. Language that promises a cancel on a moving parcel is how you get a refund and a delivery.</p>
<p>A good cancellation policy is short, operationally true, and sitting on the button. Everything else is decoration.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Should cancellations be instant?</summary><div class="faq__a"><p>Before fulfilment, yes — instant and self-serve. After fulfilment, a cancel is a return. Pretending otherwise creates double inventory and carrier chaos.</p></div></details>
<details><summary>Is a keep-offer in the cancel flow a dark pattern?</summary><div class="faq__a"><p>Not if skip is easy and the offer is real. It is a dark pattern if the cancel button is hidden, delayed, or labelled &quot;continue&quot;.</p></div></details>
<details><summary>Will a stricter policy increase chargebacks?</summary><div class="faq__a"><p>A stricter policy that you hide will. A clear policy that matches what you can actually do usually reduces them, because people know the rules before they pay.</p></div></details>
<details><summary>What about subscriptions?</summary><div class="faq__a"><p>Say when the next charge stops, whether the current shipment still goes out, and how to skip instead of cancel. Subscription cancel is a different product than one-time order cancel.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Order Edit Cut-Off Windows — Set a Deadline Before Fulfilment Starts</title>
      <link>https://ninety9.dev/blog/order-edit-cutoff-windows.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/order-edit-cutoff-windows.html</guid>
      <pubDate>Wed, 22 Jul 2026 09:00:00 GMT</pubDate>
      <category>Retention &amp; Post-Purchase</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>How long to allow self-serve Shopify order edits, how to talk about the window in emails, and what to do when the warehouse has already picked the order.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>The cut-off should be &quot;until we start fulfilling&quot;, not a random number of hours, unless your warehouse needs a buffer.</li><li>Publish the window in the confirmation email and on the order status page. A hidden deadline creates tickets, which is what you were trying to avoid.</li><li>Once pick-and-pack has started, refuse the edit and offer a second order or a return path — do not pretend you can catch a box that is already moving.</li><li>Different locations can have different windows. A 2-hour cut-off for same-day dispatch and a 24-hour window for made-to-order can coexist.</li></ul></section>
<p>An edit window is an operations contract. Marketing did not invent it; the warehouse did. The stores that get this wrong either close edits too early (tickets) or leave them open too long (boxes that have to be ripped open).</p>
<h2 id="bind-the-window-to-fulfilment-status">Bind the window to fulfilment status</h2>
<p>&quot;You can change this order until we start packing it&quot; is a sentence a warehouse can keep. &quot;You have 24 hours&quot; is a sentence that breaks the first time you offer same-day dispatch.</p>
<p>If Shopify (or your 3PL) marks an order as in fulfilment, the self-serve door closes. Everything else is a support conversation.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/reviso/icon.png" alt="Reviso: Order editing &amp; Upsell app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Reviso: Order editing &amp; Upsell</p>
<p class="appcta__text">Post-purchase self-service order editing with cancellation deflection and AI upsells.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/reviso-order-editor?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="buffer-time">Buffer time</h2>
<p>Pickers need a few minutes of certainty. If the portal allows an address change while someone is walking to the shelf, you will ship to the old address and refund the ticket. A 30–90 minute pre-pick buffer is cheaper than that.</p>
<h2 id="tell-them-once-in-the-right-places">Tell them once, in the right places</h2>
<p>Confirmation email: &quot;Need to change the address or add an item? You can do that here until we ship.&quot; Order status page: the same link, plus a disabled state after cut-off with a reason (&quot;We&#39;ve started packing this one — email us if it&#39;s urgent&quot;).</p>
<p>Silence is how you get &quot;I didn&#39;t know I could change it&quot; and &quot;I didn&#39;t know I couldn&#39;t&quot;.</p>
<h2 id="after-the-door-closes">After the door closes</h2>
<p>Do not leave a form that submits into a void. Show the closed state and the next best action: a new order, a return once it arrives, or a support email for genuine emergencies. A 404 on the edit link after cut-off is a bug.</p>
<p>The window is not a growth hack. It is how self-serve stays true.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>How many hours should the window be?</summary><div class="faq__a"><p>As long as fulfilment has not started, plus a buffer your warehouse actually needs (often 30–90 minutes before pick). A fake &quot;24 hours&quot; that you cannot honour when you ship in two is worse than a short honest window.</p></div></details>
<details><summary>What if I use a 3PL?</summary><div class="faq__a"><p>The cut-off is whenever they pull the order. Integrate with fulfilment status, not with a wall clock, or you will allow edits on orders already in a tote.</p></div></details>
<details><summary>Should the customer see a countdown?</summary><div class="faq__a"><p>A remaining time is useful if it is true. A countdown that ignores &quot;fulfilment started&quot; will unlock edits you then have to reverse.</p></div></details>
<details><summary>Can I extend the window for VIP customers?</summary><div class="faq__a"><p>Yes, with a tag-based rule. Do not extend it globally because one customer asked. The warehouse is the constraint.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>In-Cart Upsells — 9 Offers That Convert and 4 That Annoy Customers</title>
      <link>https://ninety9.dev/blog/in-cart-upsells-that-convert.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/in-cart-upsells-that-convert.html</guid>
      <pubDate>Tue, 21 Jul 2026 09:00:00 GMT</pubDate>
      <category>Cart &amp; Checkout</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>A ranked breakdown of cart upsell offer types with the price ratios, placement rules and take rates that separate the ones worth running from the ones costing you conversions.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>The cart is a confirmation surface. Offers that ask a shopper to reconsider a decision they already made will always underperform offers that simply add to it.</li><li>Price the upsell at 15 to 40 percent of the anchor product. Above that ratio it becomes a second purchase decision and gets deferred.</li><li>Two offers is the practical ceiling in a cart drawer. A third measurably lowers the take rate of the first two.</li><li>Take rate matters more than revenue in early testing, because it tells you whether the offer is relevant before volume tells you whether it is profitable.</li></ul></section>
<p>An in-cart upsell is the easiest offer in ecommerce to get slightly wrong. The shopper has committed, the mechanics are simple, and every app makes it a two-click setup — which is exactly why so many stores end up with a cart full of offers that nobody takes.</p>
<p>The difference between an offer that converts and one that gets ignored is almost never the design. It is the relationship between the offer and what is already in the basket.</p>
<h2 id="the-rule-underneath-everything">The rule underneath everything</h2>
<p>A shopper looking at their cart has finished deciding. They are in confirmation mode, not evaluation mode.</p>
<p>That single fact predicts which offers work:</p>
<ul>
<li><strong>Additive offers</strong> — &quot;would you also like…&quot; — cost the shopper nothing cognitively. They can say no in a quarter of a second and lose nothing.</li>
<li><strong>Substitutive offers</strong> — &quot;actually, consider this instead&quot; — reopen a closed decision. Even when the shopper declines, you have introduced doubt into a cart that had none.</li>
</ul>
<p>Every recommendation in this article follows from that distinction.</p>
<h2 id="the-nine-that-work">The nine that work</h2>
<h3 id="1-the-complementary-accessory">1. The complementary accessory</h3>
<p>The default, and still the best. A case with a device, a filter with a machine, socks with boots.</p>
<p>Price ratio is the variable that matters most. Somewhere between 15% and 40% of the anchor product&#39;s price is the zone where an accessory reads as &quot;obviously, yes&quot; rather than &quot;let me think about that&quot;. At 60%+ you are asking for a second purchase decision, and the shopper defers it — usually forever.</p>
<h3 id="2-the-quantity-upgrade">2. The quantity upgrade</h3>
<p>&quot;Add one more and save 10%.&quot; The shopper does not evaluate a new product at all; they evaluate a better price on something already chosen. Take rates on this are consistently the highest of any cart offer because the decision cost is close to zero.</p>
<p>Works on consumables and anything with a replenishment cycle. Does not work on considered single purchases.</p>
<h3 id="3-the-threshold-gift">3. The threshold gift</h3>
<p>Not strictly an upsell — a reward. &quot;Spend $22 more and get the travel size free.&quot;</p>
<p>What makes this work is that the shopper is choosing to <em>earn</em> something rather than to <em>buy</em> something, which is a materially different psychological transaction. The gift should be genuinely desirable and genuinely small in cost. A gift nobody wants is worse than no gift, because it reveals the mechanic.</p>
<h3 id="4-shipping-protection">4. Shipping protection</h3>
<p>Near-zero cost of goods, real perceived value, and a legitimate service. Take rates are often surprisingly high.</p>
<p>The one non-negotiable: it must be unticked by default. Pre-ticked opt-outs are illegal in the EU under the Consumer Rights Directive, generate chargebacks everywhere else, and are the fastest way to end up in a review that mentions the word &quot;sneaky&quot;.</p>
<h3 id="5-gift-wrap-and-personalisation">5. Gift wrap and personalisation</h3>
<p>Seasonal, high-margin, and it makes the order feel considered. Particularly strong in Q4, and worth turning off in February rather than leaving it running as permanent clutter.</p>
<h3 id="6-the-replenishment-nudge">6. The replenishment nudge</h3>
<p>For repeat customers only: &quot;You last ordered this 47 days ago.&quot; No discount, no persuasion — just information the shopper actually wants. It converts because it is useful rather than because it is an offer.</p>
<h3 id="7-the-bundle-completion">7. The bundle completion</h3>
<p>The shopper has two of the three items in a known set. Offering the third at a small discount completes a pattern, and pattern completion is unusually motivating.</p>
<p>Requires that the set is real and obvious to the customer. Manufactured sets do not work.</p>
<h3 id="8-the-sample-or-trial-size">8. The sample or trial size</h3>
<p>Low price, low risk, high information value for the shopper, and an excellent way to seed a future full-size purchase. Effectively a paid product trial that improves your AOV instead of costing you CAC.</p>
<h3 id="9-the-warranty-or-service-add-on">9. The warranty or service add-on</h3>
<p>Best on higher-priced items where the shopper is already thinking about protecting the purchase. Margin is usually excellent. Be precise about what it covers — vague warranties generate support load that erases the margin.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/addy/icon.png" alt="Addy: AI Cart Drawer &amp; Bundles app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Addy: AI Cart Drawer &amp; Bundles</p>
<p class="appcta__text">AI cart drawer with in-cart upsells, volume bundles, free shipping bars and per-Market targeting.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/addy-cart-drawer?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="the-four-that-annoy">The four that annoy</h2>
<h3 id="1-the-substitute-product">1. The substitute product</h3>
<p>&quot;Customers also viewed…&quot; belongs on a product page, not in a cart. Showing an alternative to something already in the basket does one of two things: nothing, or it makes the shopper wonder whether they picked the wrong item. Neither outcome is worth the slot.</p>
<h3 id="2-the-unrelated-bestseller">2. The unrelated bestseller</h3>
<p>Filling the upsell block with your top seller regardless of cart contents is the recommendation equivalent of shrugging. It trains shoppers to ignore that region of the drawer, which then poisons the well for the relevant offers you show later.</p>
<h3 id="3-email-capture-in-the-cart">3. Email capture in the cart</h3>
<p>You are roughly ninety seconds from getting their email address at checkout. Asking for it here trades a guaranteed acquisition for an interruption.</p>
<h3 id="4-the-third-fourth-and-fifth-offer">4. The third, fourth and fifth offer</h3>
<p>Every additional offer reduces the take rate of the ones above it. Two is the practical ceiling in a drawer. If you genuinely have three things worth suggesting, your recommendation logic needs to be more selective — not your cart taller.</p>
<aside class="callout callout--warn">
<p class="callout__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/></svg>The stacking trap</p>
<p>An upsell block, a gift threshold, a countdown timer, an announcement bar and a discount code field all in the same drawer is not five chances to convert. It is one confused shopper and a checkout button pushed below the fold.</p>
</aside>
<h2 id="picking-the-pairs">Picking the pairs</h2>
<p>Almost all of the performance difference between two stores running the same upsell app comes down to which products get suggested.</p>
<p>The method that works:</p>
<ol>
<li>Export the last 90 days of orders with line items.</li>
<li>For each product, count how often each other product appears in the same order.</li>
<li>Filter to pairs where the co-occurrence is materially above what random chance would produce.</li>
<li>Remove anything in the same substitutable category as the anchor.</li>
<li>Of what remains, prefer the item closest to 25% of the anchor price.</li>
</ol>
<p>That produces a better pairing list than any category rule, and it takes an afternoon. If your order volume is high enough, a system that recalculates this continuously will beat a static list, because your catalogue and your seasons move and a hand-built list does not.</p>
<h2 id="measuring-properly">Measuring properly</h2>
<p>Four numbers, in this order of usefulness:</p>
<div class="tablewrap"><table><thead><tr><th>Metric</th><th>What it tells you</th><th>When to look</th></tr></thead><tbody>
<tr><td>Take rate (accepts ÷ impressions)</td><td>Whether the offer is relevant</td><td>Immediately — needs low volume</td></tr>
<tr><td>Attach revenue per cart</td><td>Whether it is worth the slot</td><td>After ~200 impressions</td></tr>
<tr><td>Cart-to-checkout rate</td><td>Whether you added friction</td><td>Continuously, as a guardrail</td></tr>
<tr><td>Gross profit per session</td><td>Whether the whole thing is profitable</td><td>Monthly</td></tr>
</tbody></table></div>
<p>Take rate is the fastest signal, because relevance shows up long before revenue does. An offer with a take rate near zero is not underpriced — it is irrelevant, and no discount will fix that.</p>
<p>The guardrail metric is cart-to-checkout rate. If it moves down while AOV moves up, you have traded conversions for basket size. Sometimes that is profitable. Run the third number before you decide.</p>
<h2 id="a-sensible-starting-configuration">A sensible starting configuration</h2>
<p>For a store that has never run cart upsells:</p>
<ul>
<li><strong>One</strong> complementary accessory block, pairs from real order data, priced at 15–40% of the anchor.</li>
<li><strong>One</strong> free shipping progress bar above the line items.</li>
<li>Shipping protection as an unticked add-on, if you can service the claims.</li>
<li>Nothing else, for at least three weeks.</li>
</ul>
<p>Then add the second offer, and measure whether the first one&#39;s take rate dropped. If it did by more than a little, you have found your ceiling.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>What is a good in-cart upsell take rate?</summary><div class="faq__a"><p>For a relevant complementary accessory shown to every cart, single-digit percentages are normal and anything consistently above ten percent is strong. Quantity upgrades on an item already in the cart run much higher because they require no new decision. Compare each offer against your own baseline rather than against a published benchmark, since take rate depends heavily on category and price point.</p></div></details>
<details><summary>Should cart upsells be discounted?</summary><div class="faq__a"><p>Not always. A discount helps when the shopper needs a reason to decide now, and hurts when it trains customers to expect the accessory to be cheap. Test the same offer with and without a discount before assuming the discount is what makes it work.</p></div></details>
<details><summary>Where should the upsell block sit in the cart drawer?</summary><div class="faq__a"><p>Below the line items and above the trust and checkout blocks. Placing it above the line items competes with the progress bar for the top slot and makes the cart feel like an ad unit before it feels like a cart.</p></div></details>
<details><summary>Do cart upsells hurt conversion rate?</summary><div class="faq__a"><p>Additive offers placed after commitment rarely do. What hurts is anything that asks the shopper to re-open a settled decision, anything that pushes the checkout button below the fold, and anything that adds a required step. Keep the checkout path a single unobstructed click and the risk is minimal.</p></div></details>
<details><summary>How do I choose which product to upsell?</summary><div class="faq__a"><p>Use your own order data. Find the products that most frequently appear in the same order as the anchor product, filter for ones priced well below it, and exclude anything in the same substitutable category. Category-based logic produces the classic failure of recommending a second pair of the same shoes.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Self-Serve Order Edits vs Support Tickets — Why Shopify Stores Should Stop Taking Calls for Address Changes</title>
      <link>https://ninety9.dev/blog/self-serve-order-edits-vs-tickets.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/self-serve-order-edits-vs-tickets.html</guid>
      <pubDate>Tue, 21 Jul 2026 09:00:00 GMT</pubDate>
      <category>Retention &amp; Post-Purchase</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>The cost of a &quot;please change my address&quot; ticket, the edit types that should never need a human, and how to keep high-risk changes behind rules.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>Address, delivery note and &quot;add this SKU&quot; edits are ticket factories. They should be self-serve until fulfilment starts.</li><li>A human should still see high-risk changes: country switches, high-value orders, and anything that looks like freight forwarding fraud.</li><li>Measure tickets deflected, not just AOV from post-purchase upsells. The quiet win is hours returned to support.</li><li>The edit window must be obvious in the confirmation email. A portal nobody can find is not self-serve.</li></ul></section>
<p>Most &quot;order edit&quot; tickets are not judgement calls. They are a postcode, a second unit, a gift note. A person should not have to be awake for those.</p>
<p>Self-serve is not a feature for its own sake. It is how you stop paying a human to type what the customer already knows.</p>
<h2 id="what-should-never-need-a-ticket">What should never need a ticket</h2>
<p>Until fulfilment has started:</p>
<ul>
<li>Shipping address inside the same country</li>
<li>Delivery instructions</li>
<li>Adding a SKU that is in stock</li>
<li>Swapping a variant of the same product (size, colour) when inventory allows</li>
</ul>
<p>Those four are the volume. If they are still landing in the inbox, you do not have an order-edit product. You have a form letter.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/reviso/icon.png" alt="Reviso: Order editing &amp; Upsell app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Reviso: Order editing &amp; Upsell</p>
<p class="appcta__text">Post-purchase self-service order editing with cancellation deflection and AI upsells.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/reviso-order-editor?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="what-should-still-hit-a-human">What should still hit a human</h2>
<ul>
<li>Country or continent changes</li>
<li>High-value orders above a threshold you set</li>
<li>Accounts with fraud signals</li>
<li>Requests after the cut-off you published</li>
</ul>
<p>Rules are not pessimism. They are how self-serve stays available for everyone else.</p>
<h2 id="show-the-door">Show the door</h2>
<p>Confirmation email, order status page, account order history: the same link, the same window (&quot;You can change this until we ship&quot;). If the only path is &quot;reply to this email&quot;, you will get replies.</p>
<h2 id="measure-the-queue">Measure the queue</h2>
<p>Track volume of address and add-item tickets before and after. If AOV from post-purchase offers is the only number on the dashboard, you will under-invest in the thing that actually changed the team&#39;s week.</p>
<p>Self-serve <a href="/apps/reviso.html">order editing</a> is a support product that happens to be able to sell. Lead with the support.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Won&#39;t people abuse self-serve edits?</summary><div class="faq__a"><p>Some will try. Cap what can change (same country, before fulfilment, logged-in customer) and you keep the 95% of honest &quot;I typed the postcode wrong&quot; cases off the queue.</p></div></details>
<details><summary>Should every edit type be self-serve?</summary><div class="faq__a"><p>No. Cancellations with refunds, wholesale orders, and fraud-flagged accounts can stay on tickets. Self-serve is for the repetitive, low-risk majority.</p></div></details>
<details><summary>Does this replace your helpdesk?</summary><div class="faq__a"><p>It replaces the copy-paste macros. You still need humans for the rest. The point is that the rest becomes visible once the noise is gone.</p></div></details>
<details><summary>What is a typical ticket cost?</summary><div class="faq__a"><p>Even a five-minute chat is expensive at support wages, and address tickets often take more because of carrier constraints. Deflecting a few hundred a month is a real line item.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>How to A/B Test Shopify Upsell Popups Without Lying to Yourself</title>
      <link>https://ninety9.dev/blog/ab-test-shopify-popups.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/ab-test-shopify-popups.html</guid>
      <pubDate>Mon, 20 Jul 2026 09:00:00 GMT</pubDate>
      <category>CRO &amp; Analytics</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>What to test (offer, trigger, frequency), what not to test (button colour in week one), and the sample size trap that makes popup tests look like winners.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>Test one variable: the SKU, the trigger, or the cap — not all three. A &quot;new popup&quot; vs &quot;old popup&quot; test teaches you nothing.</li><li>Primary metric is attach rate of the offered SKU, plus checkout initiation, so you catch tests that add items and lose checkouts.</li><li>Do not call a winner on 200 sessions. Overlay tests are noisy; wait for a sample you would defend in a meeting.</li><li>Stop tests that hurt add-to-cart-to-checkout even if attach rate is up. A bigger basket that does not pay is not a win.</li></ul></section>
<p>Popup tests fail in a particular way: the overlay gets more clicks, AOV ticks up in the test group, checkout initiation quietly drops, and someone ships the winner. Two weeks later revenue is flat and nobody connects it.</p>
<p>You have to measure the thing you wanted (they added the SKU) and the thing you cannot afford to lose (they still paid).</p>
<h2 id="one-variable">One variable</h2>
<p>Good tests:</p>
<ul>
<li>This complementary SKU vs that one, same trigger</li>
<li>Add-to-cart trigger vs exit-intent, same SKU</li>
<li>Session cap of one vs two, same SKU and trigger</li>
</ul>
<p>Bad tests:</p>
<ul>
<li>New design + new SKU + new trigger vs the old everything</li>
<li>Button colour before the offer is even the right product</li>
</ul>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/monet/icon.png" alt="Monet • AI Popup Bundle Addons app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Monet • AI Popup Bundle Addons</p>
<p class="appcta__text">AI upsell popups triggered on add to cart, checkout initiation and exit intent.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/monet?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="metrics">Metrics</h2>
<p>Must-haves:</p>
<ul>
<li>Offer impressions</li>
<li>Offer adds (attach)</li>
<li>Checkout initiation rate</li>
<li>Conversion rate to paid order</li>
<li>Revenue per session</li>
</ul>
<p>If attach is up and checkout initiation is down, you did not find a better upsell. You found a speed bump.</p>
<h2 id="sample-size">Sample size</h2>
<p>Overlays fire on a subset of sessions. Your real n is impressions, not store sessions. A week of a small store can be hundreds of impressions, not thousands. Do not declare a 12% lift on 180 views.</p>
<p>If you cannot power the test, do not run it. Make a merchandising decision and watch revenue per session for two weeks.</p>
<h2 id="guardrails">Guardrails</h2>
<p>Hard-stop the test if checkout initiation drops beyond a threshold you set in advance. Pre-commitment is the only thing that stops a team from &quot;giving it another day&quot; on a losing overlay.</p>
<p>A/B testing is not a personality. It is a way to stop arguing. Use it when you have enough traffic to be wrong in public. Use judgement when you do not.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Should I test popup vs no popup first?</summary><div class="faq__a"><p>Yes, if you do not already know the overlay is net-positive. Many stores skip this and optimise a thing that should not exist.</p></div></details>
<details><summary>Can I use Shopify&#39;s reports for this?</summary><div class="faq__a"><p>Not for overlay-level attach. You need the app&#39;s offer analytics or a custom event (offer_shown, offer_added) in your pixel. Order value alone hides whether the popup did the work.</p></div></details>
<details><summary>How long should a test run?</summary><div class="faq__a"><p>Through at least one weekly cycle (weekday vs weekend mix). Stopping on a Tuesday because it &quot;looks good&quot; is how you ship noise.</p></div></details>
<details><summary>What if traffic is low?</summary><div class="faq__a"><p>Run fewer tests. Sequential changes with a long observation window beat a 12-variant experiment you cannot power.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Upsell Popup Design That Converts — Layout, Type and the Add Button</title>
      <link>https://ninety9.dev/blog/upsell-popup-design.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/upsell-popup-design.html</guid>
      <pubDate>Sun, 19 Jul 2026 09:00:00 GMT</pubDate>
      <category>CRO &amp; Analytics</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>The visual rules for a Shopify upsell overlay: one image, one price, one button, and the spacing that keeps it from looking like an ad.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>Match the theme&#39;s type, radius and button colour. A popup that looks like a different website gets closed as an ad.</li><li>One product image, cropped like your PDPs, not a lifestyle collage. The shopper has seconds.</li><li>The add button is the same component as add-to-cart on the product page. Do not invent a new CTA style for the overlay.</li><li>Mobile: full-width sheet from the bottom beats a centred modal that covers the product they just added.</li></ul></section>
<p>People do not read popups. They recognise them. If the overlay looks like the rest of the store, it is a question. If it looks like a template from another brand, it is an interruption.</p>
<p>Design is not decoration here. It is whether the shopper classifies the overlay as &quot;part of this purchase&quot; or &quot;an ad&quot;.</p>
<h2 id="steal-the-theme-do-not-restyle-it">Steal the theme, do not restyle it</h2>
<p>Same typeface, same button, same radius, same primary colour. The add button in the popup should be indistinguishable from add-to-cart on the product page. That continuity is the conversion feature.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/monet/icon.png" alt="Monet • AI Popup Bundle Addons app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Monet • AI Popup Bundle Addons</p>
<p class="appcta__text">AI upsell popups triggered on add to cart, checkout initiation and exit intent.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/monet?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="hierarchy">Hierarchy</h2>
<ol>
<li>Product image (one, PDP-quality, correct variant if you can)</li>
<li>Name and price</li>
<li>One line of why</li>
<li>Add</li>
<li>Dismiss</li>
</ol>
<p>If a review stars row, a countdown, three trust badges and a coupon field are in the overlay, you have built a landing page. Landing pages do not belong in a modal.</p>
<h2 id="mobile-is-a-sheet">Mobile is a sheet</h2>
<p>From the bottom, thumb-reach add button, dismiss as text under the button as well as an X. Centred desktop-style modals on a 390px screen cover the cart confirmation and panic people.</p>
<h2 id="motion">Motion</h2>
<p>200–250ms ease. No bounce. No delay on the add button. Delayed CTAs are a dark pattern and they get reported as such.</p>
<h2 id="the-test">The test</h2>
<p>Screenshot the popup on a product page. Blur the copy. Could a designer from your team tell it belongs to this store from colour and type alone? If not, stop adding offers and fix the skin.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Should popups be full-screen on mobile?</summary><div class="faq__a"><p>A bottom sheet that leaves a sliver of the page visible is enough. Full-screen feels like a hijack. They just added something — let them still see that they did.</p></div></details>
<details><summary>How much copy?</summary><div class="faq__a"><p>A title, a one-line reason (&quot;Pairs with the mug in your cart&quot;), a price. If you need a paragraph, you picked the wrong product.</p></div></details>
<details><summary>Dark overlay behind the popup?</summary><div class="faq__a"><p>Light dim, not a blackout. They should still recognise the page they were on. Continuity is why they might say yes.</p></div></details>
<details><summary>Do animations help?</summary><div class="faq__a"><p>A short ease-in is fine. Bounce, confetti and delayed buttons are how overlays start to feel like malware.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Checkout-Initiation Popups on Shopify — The Last Offer Before Payment</title>
      <link>https://ninety9.dev/blog/checkout-initiation-popups.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/checkout-initiation-popups.html</guid>
      <pubDate>Sat, 18 Jul 2026 09:00:00 GMT</pubDate>
      <category>Cart &amp; Checkout</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>How to run an upsell when the shopper clicks checkout, without delaying Shopify checkout or feeling like a hostage screen.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>A checkout-initiation offer has to be skippable in one click and fast. If it adds a second of delay, you are taxing the people who were going to pay.</li><li>The offer must be cart-aware. A generic &quot;10% off&quot; at checkout start is a coupon interstitial, not an upsell.</li><li>Never block the checkout button behind a timer or a required choice. That is a dark pattern and a conversion leak.</li><li>If they already declined the same SKU in the cart or on add-to-cart, do not ask again here.</li></ul></section>
<p>The click on checkout is the most expensive click on the site. Intercepting it is allowed only if you are almost invisible: one relevant product, one add, one obvious skip, no delay.</p>
<p>If any of those fail, you are standing in front of the till.</p>
<h2 id="instant-or-not-at-all">Instant or not at all</h2>
<p>When checkout is clicked, the overlay must already know what to show. Fetching an offer at click-time is how you introduce a spinner between &quot;I want to pay&quot; and paying. Prefetch on cart open. Cache it. Fail open: if the offer is missing, go to checkout.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/monet/icon.png" alt="Monet • AI Popup Bundle Addons app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Monet • AI Popup Bundle Addons</p>
<p class="appcta__text">AI upsell popups triggered on add to cart, checkout initiation and exit intent.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/monet?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="what-to-offer">What to offer</h2>
<p>Something small, complementary, and easy to evaluate without a PDP: a matching accessory, protection, a consumable refill. Not a new hero product. Not a subscription pitch that needs a paragraph.</p>
<p>If the cart is already above your free-shipping threshold, do not offer more cheap items &quot;to help&quot;. They are trying to leave.</p>
<h2 id="skip-must-be-the-easy-action">Skip must be the easy action</h2>
<p>A clear &quot;No thanks, continue to checkout&quot; — not a tiny X, not a 5-second lock, not a second overlay. The people who skip were going to pay. Let them.</p>
<h2 id="do-not-repeat-a-no">Do not repeat a no</h2>
<p>If this SKU was already shown on add-to-cart or in the drawer, checkout initiation is not a new conversation. Frequency cap across triggers. One offer per session still applies here — especially here.</p>
<p>Done well, this is a quiet extra few points of attach rate from people who were already sold. Done poorly, it is the reason your checkout initiation rate dropped last Tuesday and nobody knew why.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Will this slow down checkout?</summary><div class="faq__a"><p>It will if the popup waits on a slow network request. Prefetch the offer when the cart opens so checkout click can paint immediately. If you cannot be instant, do not intercept checkout.</p></div></details>
<details><summary>Is this allowed on Shopify?</summary><div class="faq__a"><p>You cannot inject into Shopify checkout itself. You can intercept the click that *starts* checkout, on your storefront, then send them on. Do not try to modify checkout.liquid in 2026.</p></div></details>
<details><summary>Should I offer a discount to stop them leaving?</summary><div class="faq__a"><p>Not at this moment. They have clicked pay. A sudden discount teaches them to click checkout to unlock a deal. Offer a complementary SKU or nothing.</p></div></details>
<details><summary>What if they have a discount code already?</summary><div class="faq__a"><p>Still fine to offer a product. Do not stack a second code. The overlay is for a SKU, not for a coupon war.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>The One-Offer Popup Rule for Shopify Upsells</title>
      <link>https://ninety9.dev/blog/one-offer-popup-rule.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/one-offer-popup-rule.html</guid>
      <pubDate>Fri, 17 Jul 2026 09:00:00 GMT</pubDate>
      <category>Bundles &amp; Upsells</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>Why a popup should sell a single thing, how to choose that thing from the cart, and what happens to conversion when you add a second product to the overlay.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>One overlay, one product, one action. A second product turns a decision into a catalogue.</li><li>Pick the offer from what is already in the cart or on the page — not from a merchandiser&#39;s favourite SKU.</li><li>The primary button is &quot;Add&quot;. The secondary action is dismiss. A &quot;See all&quot; link is how people escape into browsing.</li><li>If you cannot name why this SKU belongs with this cart in one sentence, it is not the offer.</li></ul></section>
<p>A popup has a few seconds and one job. That job is not &quot;present the catalogue&quot;. It is &quot;do you want this, yes or no&quot;.</p>
<p>Two products in the overlay doubles the reading and halves the chance of a yes. Shoppers who would have added the first SKU start comparing. Comparison is browsing. Browsing is how overlays get closed.</p>
<h2 id="one-sentence-of-why">One sentence of why</h2>
<p>Before you ship an offer, write: &quot;Because the cart contains X, we are offering Y.&quot; If X is empty (a generic sitewide popup), you do not have an upsell. You have an ad.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/monet/icon.png" alt="Monet • AI Popup Bundle Addons app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Monet • AI Popup Bundle Addons</p>
<p class="appcta__text">AI upsell popups triggered on add to cart, checkout initiation and exit intent.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/monet?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<p>Because they added the mug, we are offering the 250g beans. Because they added the running shorts, we are offering the same-brand socks. Because they added a gift-boxed candle, we are offering wrap. Those sentences are offers. &quot;Our bestsellers&quot; is not.</p>
<h2 id="the-button">The button</h2>
<p>Primary: Add (and, if you must, the price). Secondary: close. No &quot;continue shopping&quot; that opens a collection. No &quot;view product&quot; that dumps them on a PDP and kills the cart they came from.</p>
<p>If they need to read a full product page to decide, the popup picked the wrong SKU. Pick a lower-consideration attachment.</p>
<h2 id="discount-last">Discount last</h2>
<p>Try the pair at full price. If attach rate is real, keep it. If it is near zero, a small bundle-style discount can be the difference — but it should be the last lever, not the first, because popup discounts become expected.</p>
<h2 id="measure-one-number">Measure one number</h2>
<p>Attach rate of <em>this</em> SKU from <em>this</em> trigger. Not popup CTR. Clicks that do not add are still a no, they just took longer.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Can I show a bundle of two items in one popup?</summary><div class="faq__a"><p>Yes, if it is one offer — a set with one price and one add button. Two separate products with two add buttons is two offers.</p></div></details>
<details><summary>What if I have several good attachments?</summary><div class="faq__a"><p>Rank them, show the winner, keep the rest for the cart or the product page. The popup is not your merchandising dump.</p></div></details>
<details><summary>Should the popup include a discount?</summary><div class="faq__a"><p>Only if the pair does not attach at full price. Discounts in popups train people to wait for the overlay. Try without first.</p></div></details>
<details><summary>Is a carousel of offers okay?</summary><div class="faq__a"><p>No. Carousels in popups are how take rate dies. If you need a carousel, the offer is not ready.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Popup Frequency Capping on Shopify — How Often Is Too Often</title>
      <link>https://ninety9.dev/blog/popup-frequency-capping.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/popup-frequency-capping.html</guid>
      <pubDate>Thu, 16 Jul 2026 09:00:00 GMT</pubDate>
      <category>CRO &amp; Analytics</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>Session caps, cooldown after dismiss, and why showing the same upsell popup twice in one visit costs more than it makes.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>One popup per session is the default. A second showing of the same offer is a nag, not a reminder.</li><li>Honour dismiss. If they closed it, wait days, not minutes — and do not reopen it on the next page view.</li><li>Frequency caps must persist across pages in the session. A cap that resets on navigation is not a cap.</li><li>After they add the offered product, never show that popup again. The job is done.</li></ul></section>
<p>Popups do not fail because the offer is wrong. They fail because the store asked again.</p>
<p>A shopper who closes an overlay has given you an answer. Showing the same overlay on the next collection page is not persistence. It is not listening.</p>
<h2 id="the-default-cap">The default cap</h2>
<p>One impression per session, per offer. Not per page. Not per trigger. If they saw it after add-to-cart, they have seen it. Exit-intent does not get a second bite in the same visit.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/monet/icon.png" alt="Monet • AI Popup Bundle Addons app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Monet • AI Popup Bundle Addons</p>
<p class="appcta__text">AI upsell popups triggered on add to cart, checkout initiation and exit intent.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/monet?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="honour-dismiss">Honour dismiss</h2>
<p>Closed means no. Store that for the session immediately, and for a week or two if they are a returning visitor. &quot;Don&#39;t show again&quot; checkboxes that do not work are worse than no checkbox.</p>
<p>If your popup has no memory, every page view is a new interruption. Interruptions compound into a brand feeling.</p>
<h2 id="when-a-second-show-is-allowed">When a second show is allowed</h2>
<p>Rare cases:</p>
<ul>
<li>They added a <em>different</em> product and you have a <em>different</em> offer that still makes sense.</li>
<li>They came back days later and the campaign changed.</li>
<li>They completed a purchase and you are in post-purchase, which is a different surface.</li>
</ul>
<p>A second show of the <em>same</em> SKU in the <em>same</em> session is not one of the cases.</p>
<h2 id="after-a-take">After a take</h2>
<p>If they added the offered product, the popup&#39;s job is over. Kill it for the session and for that SKU. Showing &quot;add this&quot; for something already in the cart is how overlays start to look broken.</p>
<p>Frequency capping is not a setting you turn on at the end. It is the product. An offer shown once at the right moment will beat an offer shown four times at whatever moment the script fired.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Won&#39;t I miss conversions if I only show the popup once?</summary><div class="faq__a"><p>You will miss some. You will also miss the conversions you lose when shoppers start treating every overlay as an enemy. Net, one well-timed show beats three.</p></div></details>
<details><summary>What about returning visitors?</summary><div class="faq__a"><p>Cap per session, and add a longer cooldown after dismiss (7–14 days is a reasonable start). Returning visitors who already said no are not a new audience.</p></div></details>
<details><summary>Should checkout-initiation popups have a different cap?</summary><div class="faq__a"><p>They can share the session cap. If they already saw an add-to-cart offer, do not also hit them at checkout start. One offer per visit.</p></div></details>
<details><summary>How do I test this?</summary><div class="faq__a"><p>Track take rate, add-to-cart rate after dismiss, and popup-close rate. If close rate climbs while take rate falls, you are showing it too often.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Cart Goals for Shopify Subscription Products</title>
      <link>https://ninety9.dev/blog/cart-goals-for-subscriptions.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/cart-goals-for-subscriptions.html</guid>
      <pubDate>Wed, 15 Jul 2026 09:00:00 GMT</pubDate>
      <category>Shipping &amp; Fulfilment</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>How to run a free-shipping or free-gift progress bar when the cart contains subscribing items, prepaid plans, or a mix of one-time and recurring SKUs.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>Count the amount that will actually be charged today, not the lifetime value of the subscription. The bar is about this shipment.</li><li>Mixed carts (subscribe + one-time) should use one remaining amount based on the current charge. Splitting two bars is how shoppers stall.</li><li>Do not offer a &quot;free gift with subscription&quot; that only applies on order 4. If it is not in this box, it is not a cart goal.</li><li>Prepaid plans (3-month, 6-month) can use a quantity-style goal (&quot;add a one-time extra&quot;) rather than a shipping threshold that the prepaid already clears.</li></ul></section>
<p>Subscription carts lie to naive progress bars. A $30/month coffee plan looks like a $30 cart, or like a $360 cart, depending on whether the app reads the selling plan or the product price. Both mistakes produce a remaining amount that does not match checkout.</p>
<p>The bar has to follow the money that moves today.</p>
<h2 id="this-shipment-not-this-relationship">This shipment, not this relationship</h2>
<p>Free shipping is about the box leaving today. A 12-month prepaid that ships monthly is not twelve boxes of postage credit. A subscribe-and-save item that charges $28 today is a $28 line, even if the merchant thinks in LTV.</p>
<p>If your goal is &quot;free shipping over $50&quot;, a subscriber at $28 is $22 away — unless your policy actually ships subscribers free, in which case the bar should say that and stop pretending they need $22.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/goalify/icon.png" alt="Goalify: Free Shipping Bar PRO app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Goalify: Free Shipping Bar PRO</p>
<p class="appcta__text">Free shipping bars, free gifts, BOGO and AI upsells driven by stackable cart goals.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/goalify?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="mixed-carts">Mixed carts</h2>
<p>One remaining amount. Always. A shopper with a $28 subscription and a $20 one-time bag of beans has a $48 today-total. If the threshold is $50, they are $2 away. Two bars (&quot;subscription goal&quot; and &quot;one-time goal&quot;) is how you lose the add.</p>
<p>If your policy is &quot;subscribers always ship free&quot;, the mixed cart is already unlocked. Offer a gift or a one-time extra as the second goal, or hide the bar.</p>
<h2 id="gifts-that-arrive-later">Gifts that arrive later</h2>
<p>&quot;Free mug on your third shipment&quot; is a retention offer. It is not a cart goal. Putting it on the progress bar implies it is in <em>this</em> order. When it is not, you get a ticket.</p>
<p>If you want a goal that pulls AOV on order one, the reward has to ship with order one.</p>
<h2 id="test-the-ugly-cart">Test the ugly cart</h2>
<p>Before you launch, add: a subscribe item, a prepaid plan, a one-time SKU, a discount code, and a market with a different currency. If the remaining amount still matches the checkout summary, the bar is safe. If not, fix the total — do not launch a pretty bar on a wrong number.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Should the shipping bar include future subscription charges?</summary><div class="faq__a"><p>No. The shopper is paying for this delivery. Future charges are not in this parcel and should not unlock this parcel&#39;s shipping.</p></div></details>
<details><summary>What if subscription items already qualify for free shipping?</summary><div class="faq__a"><p>Then the bar&#39;s job on those carts is a second goal — a one-time add-on or gift — or the bar should hide. A bar that says &quot;$0 away&quot; as a permanent state is dead chrome.</p></div></details>
<details><summary>Can I require a subscription to unlock free shipping?</summary><div class="faq__a"><p>You can. Make that rule explicit in the bar (&quot;Subscribe and this order ships free&quot;) so a one-time buyer is not staring at a number they cannot reach without changing selling plan.</p></div></details>
<details><summary>Do selling plans break progress bars?</summary><div class="faq__a"><p>They do if the app totals list price instead of current charge, or ignores selling plans entirely. Test a mixed cart before you ship.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>The Free Shipping Progress Bar — Placement, Copy and the Psychology</title>
      <link>https://ninety9.dev/blog/free-shipping-progress-bar-guide.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/free-shipping-progress-bar-guide.html</guid>
      <pubDate>Tue, 14 Jul 2026 09:00:00 GMT</pubDate>
      <category>Shipping &amp; Fulfilment</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>Why a progress bar outperforms a static shipping policy, the exact copy patterns that convert, where to place it, and how to handle the moment the goal is met.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>Show the remaining amount, not the target. &quot;You are $14 away&quot; outperforms &quot;Free shipping over $70&quot; because one is a task and the other is a rule.</li><li>The goal-gradient effect means motivation increases as the bar fills, which is why the visual matters and a text-only statement underperforms.</li><li>Placement order by value - cart drawer, cart page, product page, sticky site bar. Most stores get the most from the first alone.</li><li>The completion state is not a formality. Celebrating the crossing is what makes the next goal in a stacked ladder credible.</li></ul></section>
<p>A free shipping threshold is a number. A progress bar is what turns that number into behaviour.</p>
<p>The distinction matters because plenty of stores set a sensible threshold, state it in the footer and on a shipping policy page, and then wonder why it did not move <a href="/blog/how-to-increase-average-order-value-shopify.html">average order value</a>. The threshold was never the active ingredient. Visibility was.</p>
<h2 id="why-a-bar-beats-a-statement">Why a bar beats a statement</h2>
<p>Three well-documented effects are doing the work.</p>
<p><strong>The goal-gradient effect.</strong> Motivation to complete a task increases as perceived distance to completion decreases. A shopper at 80% of a threshold is substantially more motivated than one at 30% — and neither is motivated at all if they cannot see where they stand.</p>
<p><strong>The endowed progress effect.</strong> People are more likely to complete a task they have already started. A cart with anything in it has already started; the bar makes that visible.</p>
<p><strong>Loss framing.</strong> &quot;You&#39;re $14 away from free shipping&quot; implicitly frames not adding as losing something. That is a stronger motivator than gaining the equivalent amount.</p>
<p>None of these operate on a static policy statement, because a policy is information and a bar is feedback.</p>
<h2 id="the-copy-that-works">The copy that works</h2>
<p>The pattern is consistent enough to be a rule.</p>
<div class="tablewrap"><table><thead><tr><th>Copy</th><th>Why it works or does not</th></tr></thead><tbody>
<tr><td>&quot;Free shipping over $70&quot;</td><td>Weakest. A rule about you, requiring the shopper to do arithmetic.</td></tr>
<tr><td>&quot;Spend $70 for free shipping&quot;</td><td>Better, still a target rather than a gap.</td></tr>
<tr><td>&quot;You&#39;re $14 away from free shipping&quot;</td><td><strong>Strong.</strong> Second person, specific gap, immediate.</td></tr>
<tr><td>&quot;Add $14 more to unlock free shipping&quot;</td><td><strong>Strong.</strong> Adds a verb and the word &quot;unlock&quot;.</td></tr>
<tr><td>&quot;Almost there! Free shipping soon&quot;</td><td>Weak. No number, so no goal.</td></tr>
<tr><td>&quot;🎉 You&#39;ve unlocked free shipping!&quot;</td><td><strong>Correct completion state.</strong> Names the achievement.</td></tr>
</tbody></table></div>
<p>Four principles behind those:</p>
<ol>
<li><strong>Second person.</strong> &quot;You&quot; outperforms passive or store-centric phrasing.</li>
<li><strong>The gap, not the target.</strong> The remaining amount is the number that matters.</li>
<li><strong>The shopper&#39;s currency</strong>, converted and rounded properly. A gap expressed in a foreign currency is not a goal.</li>
<li><strong>A verb.</strong> &quot;Add&quot;, &quot;unlock&quot;, &quot;reach&quot; all outperform a bare statement.</li>
</ol>
<aside class="callout callout--key">
<p class="callout__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 7a5 5 0 1 0-4.5 5H12v3h3v3h4v-4l-4-4V7Z"/></svg>The one-line test</p>
<p>Read your bar copy out loud as if speaking to someone standing in front of you. If it sounds like a sign on a wall rather than a sentence you would say, rewrite it.</p>
</aside>
<h2 id="placement-in-order-of-value">Placement, in order of value</h2>
<h3 id="1-the-cart-drawer">1. The cart drawer</h3>
<p>The highest-value position by a wide margin. This is where the shopper evaluates their basket as a whole, and it is the only place where &quot;add one more thing&quot; is a one-click action rather than a navigation.</p>
<p>Put it at the very top, above the line items. Below them it reads as a footnote. Above them it reads as an instruction, and it frames everything beneath.</p>
<h3 id="2-the-cart-page">2. The cart page</h3>
<p>Same logic, for stores that use a full cart page or for shoppers who navigate there directly.</p>
<h3 id="3-the-product-page">3. The product page</h3>
<p>Useful, with a caveat. It works well for stores where shoppers commonly add one item and head straight for checkout, because it introduces the goal before the cart even opens.</p>
<p>The caveat: do not show a bar at 0% to a shopper with an empty cart. An empty progress bar communicates distance, not opportunity. Either hide it until the cart has something in it, or show the threshold as a plain statement until there is progress to display.</p>
<h3 id="4-a-sticky-site-wide-bar">4. A sticky site-wide bar</h3>
<p>Reinforcement rather than a driver. It keeps the offer present while browsing, which has value, but the conversion moment happens in the cart. Treat it as a reminder, not as the main implementation.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/goalify/icon.png" alt="Goalify: Free Shipping Bar PRO app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Goalify: Free Shipping Bar PRO</p>
<p class="appcta__text">Free shipping bars, free gifts, BOGO and AI upsells driven by stackable cart goals.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/goalify?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="the-completion-state">The completion state</h2>
<p>Most implementations treat crossing the threshold as an end state — the bar fills, the message changes, done.</p>
<p>That is a missed opportunity, for two reasons.</p>
<p><strong>Celebration reinforces credibility.</strong> A visible, slightly emphatic completion state teaches the shopper that goals in your store are real and reachable. That matters if you run more than one.</p>
<p><strong>The next goal should appear immediately.</strong> A shopper who lands at $73 on a $70 threshold has, at that moment, no further reason to add anything. If a second tier exists — an order discount at $110, a free gift at $150 — it should appear the instant the first is met.</p>
<p>That is the difference between a threshold that captures one behaviour change and a mechanic that keeps working across the whole upper half of your order distribution.</p>
<h2 id="design-details-that-matter">Design details that matter</h2>
<ul>
<li><strong>Animate the fill</strong>, briefly. A bar that jumps instantly is less noticeable than one that visibly moves. Two hundred milliseconds is plenty.</li>
<li><strong>Announce changes to screen readers.</strong> Use <code>aria-live=&quot;polite&quot;</code> on the message so the update is spoken. This is a genuine accessibility requirement, not a nicety.</li>
<li><strong>Respect reduced motion.</strong> Users with <code>prefers-reduced-motion</code> set should get the state change without the animation.</li>
<li><strong>Contrast the fill against the track.</strong> A bar that is hard to read at a glance defeats the purpose.</li>
<li><strong>Keep it one line on mobile.</strong> Two-line bar copy on a phone eats vertical space in the cart drawer that line items need.</li>
<li><strong>Never let it push the checkout button below the fold.</strong> The bar supports the primary action; it does not compete with it.</li>
</ul>
<aside class="callout callout--warn">
<p class="callout__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/></svg>The empty-cart bar</p>
<p>A progress bar showing &quot;$70 away from free shipping&quot; to someone with an empty cart is one of the most common implementation mistakes. It frames your shipping policy as a large distance at the exact moment the shopper has invested nothing. Suppress it until there is at least one item.</p>
</aside>
<h2 id="localisation">Localisation</h2>
<p>The bar is one of the most-read strings on your site, which makes untranslated bar copy unusually damaging.</p>
<p>Three requirements:</p>
<ul>
<li><strong>Translated copy</strong> for every language you sell in, not just your top two.</li>
<li><strong>Converted and rounded amounts</strong> in the shopper&#39;s presentment currency. A gap of &quot;€12.83&quot; reads as a machine output; &quot;€13&quot; reads as a goal.</li>
<li><strong>Per-market thresholds.</strong> The threshold behind the bar should be set from that market&#39;s own order distribution and shipping cost, not converted from your domestic number.</li>
</ul>
<h2 id="what-to-measure">What to measure</h2>
<p>Not average order value on its own — a threshold raises AOV mechanically.</p>
<ul>
<li><strong>Share of orders above the threshold</strong>, before and after. The direct behaviour measure.</li>
<li><strong>Median order value</strong>, which is more honest than the mean for this purpose.</li>
<li><strong>Cart-to-checkout rate.</strong> The guardrail. A bar that adds visual clutter without a well-set threshold can cost conversions.</li>
<li><strong>Distance-at-checkout distribution.</strong> Underused and very informative: how far from the threshold were the orders that did <em>not</em> cross it? If most sit just below, your threshold is slightly too high. If most are far below, it is much too high.</li>
</ul>
<p>That last chart is the fastest diagnostic there is for a threshold that needs adjusting, and almost nobody looks at it.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Does a free shipping progress bar actually increase average order value?</summary><div class="faq__a"><p>Consistently, when the threshold is set correctly. The bar itself does not create the effect - the threshold does - but the bar is what makes the threshold visible and personal, and a threshold nobody can see their progress against behaves like a policy rather than a goal.</p></div></details>
<details><summary>Where should the free shipping bar be placed?</summary><div class="faq__a"><p>The cart drawer produces the most value because that is where the shopper is evaluating their basket as a whole. The cart page is second, the product page third. A sticky site-wide bar is useful reinforcement but rarely the main driver.</p></div></details>
<details><summary>What should the progress bar say?</summary><div class="faq__a"><p>The remaining amount in the shopper&#39;s currency, phrased in the second person. &quot;You are $14 away from free shipping&quot; is the base pattern. Avoid stating the threshold on its own, and avoid vague language like &quot;almost there&quot; without a number.</p></div></details>
<details><summary>Should the bar show on the product page too?</summary><div class="faq__a"><p>It can help, particularly for stores where shoppers add a single item and go straight to checkout. Be careful that it does not appear before anything is in the cart, where a bar at zero percent is discouraging rather than motivating.</p></div></details>
<details><summary>What happens after the shopper reaches the goal?</summary><div class="faq__a"><p>Two things should happen. The bar should visibly celebrate the crossing, which reinforces that goals in your store are real. And if you run stacked goals, the next tier should immediately appear so the mechanic keeps working above the first threshold.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>When Not to Offer Free Shipping on Shopify</title>
      <link>https://ninety9.dev/blog/when-not-to-offer-free-shipping.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/when-not-to-offer-free-shipping.html</guid>
      <pubDate>Tue, 14 Jul 2026 09:00:00 GMT</pubDate>
      <category>Shipping &amp; Fulfilment</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>The stores, categories and order profiles where a free shipping threshold destroys margin, and what to run instead of &quot;free over $X&quot;.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>Free shipping is not a law of ecommerce. It is a discount on fulfilment. If your average parcel costs more than the lift in AOV, you are buying orders with shipping.</li><li>Heavy, low-price, or made-to-order goods often should not run a threshold at all. Show a delivery estimate and a paid rate instead.</li><li>A threshold set below the cost of the average parcel is a leak, not a goal.</li><li>Alternatives that often win: flat rate, free on this collection, or a progress bar aimed at a gift rather than postage.</li></ul></section>
<p>The internet treats free shipping as mandatory. Your P&amp;L does not. A threshold that sits under the real cost of the box is not a growth lever. It is a recurring discount you forgot to name.</p>
<h2 id="when-a-threshold-is-the-wrong-product">When a threshold is the wrong product</h2>
<p>Skip store-wide free shipping, or set it very high, when:</p>
<ul>
<li>Average parcel cost is a large share of AOV (heavy goods, bulky packaging, cold chain).</li>
<li>You drop-ship internationally and postage is a surprise until checkout.</li>
<li>Orders are frequently a single low-price item that will never reach a sane threshold.</li>
<li>You already lose money on the first unit and make it up on repeat — then you are stacking two discounts.</li>
</ul>
<p>In those stores a progress bar toward &quot;free&quot; trains people to add junk they will return, which is worse than paid shipping.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/goalify/icon.png" alt="Goalify: Free Shipping Bar PRO app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Goalify: Free Shipping Bar PRO</p>
<p class="appcta__text">Free shipping bars, free gifts, BOGO and AI upsells driven by stackable cart goals.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/goalify?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="what-to-run-instead">What to run instead</h2>
<p><strong>Honest paid rates</strong> plus a delivery date. &quot;Arrives Thursday · $6.50&quot; converts better than a fake free that appears as $8.00 at checkout.</p>
<p><strong>Collection-level free shipping.</strong> The light, high-margin line can carry a bar. The heavy line cannot. Two policies beat one lie.</p>
<p><strong>A gift goal, not a postage goal.</strong> If you want the psychology of a threshold without giving away fulfilment, unlock a sample, wrap, or accessory. You still get the &quot;I&#39;m $9 away&quot; behaviour. You do not pretend DHL is free.</p>
<h2 id="if-you-already-promised-it">If you already promised it</h2>
<p>Do not rip the bar out this afternoon. Raise the threshold toward a number that clears parcel cost plus a margin of safety, grandfather the old number in ads until they expire, and move the bar copy to remaining amount so the new number still feels like a task.</p>
<p>Free shipping is a campaign you can choose. It is not a feature of Shopify.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Doesn&#39;t everyone expect free shipping?</summary><div class="faq__a"><p>Many shoppers prefer it, especially in the US. That is not the same as &quot;it is profitable for you&quot;. If your category is furniture, food, or international drop-ship, the expectation is weaker than the cost.</p></div></details>
<details><summary>Can I offer free shipping only on some products?</summary><div class="faq__a"><p>Yes, and you should if the rest of the catalogue cannot absorb postage. A collection-level promise is more honest than a store-wide bar that excludes half the SKUs in the small print.</p></div></details>
<details><summary>What do I put in the cart instead of a shipping bar?</summary><div class="faq__a"><p>A delivery estimate, a clear paid rate, and if you still want a goal, a free gift or a quantity break that does not pretend postage is free.</p></div></details>
<details><summary>Is &quot;free shipping&quot; in ads a problem if I do not offer it on the store?</summary><div class="faq__a"><p>Yes. Do not advertise a threshold you do not honour. That is a policy problem, not a cart-app problem.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Where to Put a Free Shipping Progress Bar on Shopify</title>
      <link>https://ninety9.dev/blog/where-to-put-a-progress-bar.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/where-to-put-a-progress-bar.html</guid>
      <pubDate>Mon, 13 Jul 2026 09:00:00 GMT</pubDate>
      <category>Shipping &amp; Fulfilment</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>Cart drawer, product page, header, or all three — the placements that increase average order value and the ones that just add clutter.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>The cart is the primary surface. If the bar is not visible when the basket is open, nothing else you do with it matters.</li><li>A product-page bar is a preview, not a second cart. It should use the same remaining amount as the cart, including the item they are looking at if you can do that honestly.</li><li>A site-wide header bar is optional. It helps when the threshold is a brand promise; it nags when it follows the shopper into blog posts and the account page.</li><li>Never run two different numbers in two places. Desync is worse than no bar.</li></ul></section>
<p>A progress bar that the shopper cannot see at the moment they are deciding whether to add one more item is a settings page, not a conversion feature.</p>
<p>Start in the cart. Add other surfaces only if they share the same number.</p>
<h2 id="the-cart-comes-first">The cart comes first</h2>
<p>In a drawer, the bar goes at the very top, above line items. In a cart page, the same: full width, first. This is the moment of highest intent and the moment the remaining amount is real.</p>
<p>If you only have engineering time for one placement, this is it.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/goalify/icon.png" alt="Goalify: Free Shipping Bar PRO app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Goalify: Free Shipping Bar PRO</p>
<p class="appcta__text">Free shipping bars, free gifts, BOGO and AI upsells driven by stackable cart goals.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/goalify?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="product-page-as-a-preview">Product page as a preview</h2>
<p>A slim bar on the product page is useful when it answers &quot;if I add this, do I get free shipping?&quot; That requires either:</p>
<ul>
<li>Showing remaining <em>without</em> the current item, clearly labelled, or</li>
<li>Showing remaining <em>with</em> the current item, clearly labelled as a preview.</li>
</ul>
<p>An unlabelled number that assumes they have added something they have not is how you get angry emails and a support macro.</p>
<h2 id="header-bars">Header bars</h2>
<p>A header announcement works for a campaign (&quot;Free shipping over $50 this weekend&quot;) and poorly as a forever element. Permanent header bars become wallpaper. Wallpaper does not move AOV.</p>
<p>If you use one, hide it on cart and checkout so you do not show the same message twice, and hide it on content pages where it has nothing to do.</p>
<h2 id="the-desync-problem">The desync problem</h2>
<p>Two bars with two remaining amounts — because one includes discounts and one does not, or one is cached — will be noticed. Shoppers compare. When they do not match, they believe neither, and they will not add the extra item.</p>
<p>One calculator. Many views. That is the whole architecture.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Should the bar be on every page?</summary><div class="faq__a"><p>No. Cart, product, collection if you must. Not the blog, not the policy pages, not checkout (Shopify owns checkout).</p></div></details>
<details><summary>Can the product-page bar include the current item before it is added?</summary><div class="faq__a"><p>Only if you make that obvious (&quot;If you add this, you&#39;ll be $8 away&quot;). A silent hypothetical remaining amount is a lie when they have not added it yet.</p></div></details>
<details><summary>Sticky header bars — good or bad?</summary><div class="faq__a"><p>Good on collection and product during a shipping promotion. Bad as a permanent chrome element. Shoppers learn to ignore chrome.</p></div></details>
<details><summary>What if I use a cart page, not a drawer?</summary><div class="faq__a"><p>Put the bar at the top of the cart page, full width, before line items. The same remaining-amount copy applies.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Free Shipping Bar Copy — Write the Remaining Amount, Not the Rule</title>
      <link>https://ninety9.dev/blog/free-shipping-bar-copy.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/free-shipping-bar-copy.html</guid>
      <pubDate>Sun, 12 Jul 2026 09:00:00 GMT</pubDate>
      <category>Shipping &amp; Fulfilment</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>Why &quot;Free shipping over $70&quot; underperforms &quot;You&apos;re $14 away&quot;, and the three lines that belong on a Shopify progress bar.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>Remaining amount beats target amount. &quot;You&#39;re $14 away&quot; is a task; &quot;Free shipping over $70&quot; is a policy.</li><li>Once the goal is met, change the line. A bar that still talks about the threshold after it is unlocked feels broken.</li><li>Do not mix currency, percentage and gift copy in one sentence. One number, one outcome.</li><li>Translate the line per market. A US remaining-amount line pasted into a EUR store looks lazy and often rounds wrong.</li></ul></section>
<p>A free shipping threshold is a rule. A progress bar is a task. Shoppers complete tasks. They do not memorise rules.</p>
<p>That is why &quot;Free shipping over $70&quot; is weak copy even when the maths is perfect. It describes your policy. &quot;You&#39;re $14 away from free shipping&quot; describes <em>their</em> next move.</p>
<h2 id="the-three-states">The three states</h2>
<p>Write three lines, not one:</p>
<ol>
<li><strong>Empty cart / far from goal.</strong> &quot;Free shipping at $70 — add $70 to get it.&quot; (Only here is the target useful, because remaining ≈ target.)</li>
<li><strong>In progress.</strong> &quot;You&#39;re $14 away from free shipping.&quot;</li>
<li><strong>Unlocked.</strong> &quot;Free shipping unlocked on this order.&quot;</li>
</ol>
<p>If you have a second goal, the unlocked state becomes the setup for it: &quot;Free shipping unlocked. $6 away from a free gift.&quot;</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/goalify/icon.png" alt="Goalify: Free Shipping Bar PRO app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Goalify: Free Shipping Bar PRO</p>
<p class="appcta__text">Free shipping bars, free gifts, BOGO and AI upsells driven by stackable cart goals.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/goalify?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="one-number">One number</h2>
<p>Do not write &quot;You&#39;re 18% away&quot; or &quot;Add 2 more items&quot;. Percentages hide the actual spend; item counts break as soon as prices differ. Money remaining is the only number that matches the checkout the shopper is about to see.</p>
<p>Match the currency and the rounding of the cart. If Shopify shows €11.40, the bar cannot show €12.</p>
<h2 id="markets">Markets</h2>
<p>Every market needs its own threshold <em>and</em> its own sentence. Translating &quot;You&#39;re $14 away&quot; into a language that does not use that construction, or leaving the dollar sign on a kroner store, is how bars start to look like apps.</p>
<p>If you cannot staff translations, keep the structure and swap the currency. Structure travels. Slang does not.</p>
<h2 id="what-not-to-say">What not to say</h2>
<ul>
<li>&quot;Spend more to save on shipping&quot; — lecture.</li>
<li>&quot;Hurry, almost there!&quot; without a number — noise.</li>
<li>&quot;Free shipping&quot; when they still have $40 to go — a false unlock.</li>
</ul>
<p>The bar has one job: tell them how much is left. Do that job in the fewest honest words.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Should the bar show the target or the remaining amount?</summary><div class="faq__a"><p>Remaining amount, always, until the goal is met. After that, a confirmation (&quot;Free shipping unlocked&quot;) plus the next goal if you have one.</p></div></details>
<details><summary>How precise should the remaining amount be?</summary><div class="faq__a"><p>To the cent in the shopper&#39;s currency, using the same rounding as the cart. A bar that says &quot;$12 away&quot; when the cart says $11.40 left is a small lie with a large cost.</p></div></details>
<details><summary>Can I add humour?</summary><div class="faq__a"><p>A little, if it is on-brand and the number stays first. Humour that hides the remaining amount is decoration, and decoration gets skipped.</p></div></details>
<details><summary>Where does the bar go if copy is this important?</summary><div class="faq__a"><p>Top of the cart, and optionally a slim bar on the product page that uses the same remaining-amount logic. Different copy in two places is how you get two numbers.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Gift Wrap as a Shopify Cart Goal — A Second Threshold That Does Not Cheapen Shipping</title>
      <link>https://ninety9.dev/blog/gift-wrap-as-a-cart-goal.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/gift-wrap-as-a-cart-goal.html</guid>
      <pubDate>Sat, 11 Jul 2026 09:00:00 GMT</pubDate>
      <category>Shipping &amp; Fulfilment</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>How to add gift wrapping as a progress-bar goal without competing with free shipping, and the copy that makes it feel like a treat instead of a fee.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>Gift wrap works as a stacked goal only if it sits above free shipping, or only runs when free shipping is already met.</li><li>Price it as a small, round add-on. A $4.95 wrap on a $28 order feels like a tax; a $2 wrap on a $70 order feels like a finishing touch.</li><li>Seasonal windows outperform year-round wrap. Turn it off when nobody is gifting.</li><li>The bar copy should name the wrap, not &quot;a surprise gift&quot;. Shoppers need to know what they are unlocking.</li></ul></section>
<p>Gift wrap is one of the few add-ons that makes the order feel more expensive in a good way. Used as a cart goal, it can also pull a basket over a small gap without touching your shipping policy.</p>
<p>Used badly, it is a second progress bar fighting the first, and the shopper ignores both.</p>
<h2 id="the-stacking-rule">The stacking rule</h2>
<p>A progress bar can show more than one goal. It cannot show two incomplete goals of similar size without becoming noise.</p>
<p>The clean pattern:</p>
<ol>
<li>Free shipping is the first goal, always.</li>
<li>Gift wrap included is the second goal, and it only lights up once shipping is met — or it is a small increment <em>above</em> the shipping threshold.</li>
</ol>
<p>&quot; $8 from free shipping, $11 from wrap&quot; is two chores. &quot;Free shipping unlocked — $6 from gift wrap included&quot; is a treat on top of a win.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/goalify/icon.png" alt="Goalify: Free Shipping Bar PRO app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Goalify: Free Shipping Bar PRO</p>
<p class="appcta__text">Free shipping bars, free gifts, BOGO and AI upsells driven by stackable cart goals.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/goalify?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="price-it-like-a-finishing-touch">Price it like a finishing touch</h2>
<p>Wrap has to be cheap relative to the basket you want. On a $30 order, $5 wrap is 17%. That is a tax. On a $75 order, $2–3 wrap is a rounding error that feels generous when it is included.</p>
<p>If you cannot include wrap at a price that is small, do not make it a goal. Sell it as an optional line and leave the bar for shipping.</p>
<h2 id="seasonality-is-the-feature">Seasonality is the feature</h2>
<p>Run wrap as a goal from mid-November through the first week of January, plus the week before Valentine&#39;s and Mother&#39;s Day if those matter to you. The rest of the year, hide it. A wrap goal in March trains people to ignore the bar.</p>
<h2 id="operations-veto">Operations veto</h2>
<p>If the warehouse cannot wrap in peak week, the marketing is a lie. Confirm capacity before you turn the goal on. A progress bar is a promise. Promises that break at fulfilment become tickets, not AOV.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Should gift wrap be a product or a cart goal?</summary><div class="faq__a"><p>If you want it on every order as an optional line, it can be a simple add-on. If you want it to pull the basket over a threshold, make it a goal on the progress bar — &quot; $6 away from gift wrap included&quot;.</p></div></details>
<details><summary>Does wrap compete with free shipping?</summary><div class="faq__a"><p>It does if both are incomplete at once. Stack wrap as a second goal after free shipping is earned, or run wrap only in gift-heavy months when shipping is already free above a low threshold.</p></div></details>
<details><summary>What about personalisation notes?</summary><div class="faq__a"><p>Offer a note field once wrap is in the cart. Do not ask for a message before they have said yes to wrap — that is two commitments instead of one.</p></div></details>
<details><summary>Will this slow fulfilment?</summary><div class="faq__a"><p>Only if operations cannot do it. Do not sell wrap you cannot staff in week 50. A goal you fail in December costs more than it made in November.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Cross-Sell vs Upsell on the Shopify Product Page</title>
      <link>https://ninety9.dev/blog/cross-sell-vs-upsell-product-page.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/cross-sell-vs-upsell-product-page.html</guid>
      <pubDate>Fri, 10 Jul 2026 09:00:00 GMT</pubDate>
      <category>Bundles &amp; Upsells</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>The difference between selling more and selling up, which one belongs next to the buy box, and why mixing them in one widget makes both worse.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>An upsell is a better version of the same job (larger size, higher tier, premium material). A cross-sell is a different job that pairs with this one.</li><li>The product page can carry one of each at most, and they must not share a widget.</li><li>Upsells belong in the buy box as a variant or plan choice. Cross-sells belong under add-to-cart as an optional extra.</li><li>If you only have room for one, pick the one with the higher margin on the attached revenue, not the higher click rate.</li></ul></section>
<p>People use &quot;upsell&quot; to mean anything that makes the basket bigger. That sloppiness shows up on product pages as a single widget trying to sell a larger size, a matching accessory and a totally different product from another collection. That widget is not a strategy. It is a tray.</p>
<p>Separate the jobs.</p>
<h2 id="upsell-a-better-version-of-this">Upsell: a better version of this</h2>
<p>The shopper already wants this job done. You are offering a better way to do it: more volume, a longer subscription, a premium material, a warranty that attaches to this item.</p>
<p>The UI is a choice <em>inside</em> the buy box. Plans, sizes, &quot;upgrade to the kit&quot;. Not a second product card.</p>
<h2 id="cross-sell-a-second-job">Cross-sell: a second job</h2>
<p>The shopper wants this, and also might want that: the belt with the trousers, the beans with the mug, the case with the phone.</p>
<p>The UI is an optional extra <em>under</em> add-to-cart. A checkbox or a single card. Not a swap of the thing they came for.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/addly/icon.png" alt="Addly: AI Bundles app &amp; Upsell app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Addly: AI Bundles app &amp; Upsell</p>
<p class="appcta__text">Product bundles, quantity breaks, volume discounts and AI-recommended offers for Shopify.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/addly?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="one-widget-cannot-do-both">One widget cannot do both</h2>
<p>A carousel titled &quot;You may also like&quot; that contains a larger size, a colourway and a random accessory is three strategies colliding. The larger size should have been a variant. The colourway is not an offer. The accessory might be a cross-sell if it is actually complementary.</p>
<p>Clean it until each block has one sentence of purpose. If you cannot write the sentence, delete the block.</p>
<h2 id="if-you-only-have-one-slot">If you only have one slot</h2>
<p>Compute attached revenue × margin for the best upsell and the best cross-sell over the last 90 days. Ship the winner. Curiosity about the loser is how pages get noisy again.</p>
<p>The cart can take the other job later. The product page does not have to.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Is a larger pack size an upsell or a quantity break?</summary><div class="faq__a"><p>It is an upsell if it is a different SKU (500ml vs 250ml). It is a quantity break if it is two of the same SKU. Do not treat them as the same offer.</p></div></details>
<details><summary>Should I upsell before the shopper has added to cart?</summary><div class="faq__a"><p>Yes — that is the point of a buy-box upsell. After add-to-cart, you are in cross-sell territory (the cart, a popup, a post-purchase offer).</p></div></details>
<details><summary>Why do mixed widgets convert poorly?</summary><div class="faq__a"><p>Because they ask two questions at once: &quot;Do you want a better version?&quot; and &quot;Do you want something else?&quot; Shoppers skip widgets that feel like a second catalogue.</p></div></details>
<details><summary>Can AI pick between upsell and cross-sell?</summary><div class="faq__a"><p>It can rank candidates inside one type. The type is a merchandising decision. Do not outsource that.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Where to Place Bundles on a Shopify Product Page</title>
      <link>https://ninety9.dev/blog/where-to-place-bundles-on-pdp.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/where-to-place-bundles-on-pdp.html</guid>
      <pubDate>Thu, 09 Jul 2026 09:00:00 GMT</pubDate>
      <category>Bundles &amp; Upsells</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>Above the fold, in the buy box, or below the description — the three placements for a bundle offer and the one that consistently gets the add.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>If the bundle is the product, it belongs in the buy box — a selector, not a widget under the description.</li><li>If the bundle is an optional upgrade to a single SKU, it belongs immediately under add-to-cart, still above the fold on desktop.</li><li>Footer carousels and &quot;you may also like&quot; rows are for discovery, not for this purchase. They convert like related products, because they are related products.</li><li>Never show the same bundle in two places on one page. The second sighting feels like an ad.</li></ul></section>
<p>Placement is not decoration. A bundle the shopper does not see is a bundle you did not offer.</p>
<p>Most Shopify product pages bury the offer in a related-products row that starts after the description, the size guide and two app blocks. That row converts like a related-products row. Which is to say: barely, and mostly on sessions that were going to browse anyway.</p>
<h2 id="three-jobs-three-places">Three jobs, three places</h2>
<p><strong>The bundle is the product.</strong> The shopper landed here to buy a set. The buy box is a bundle selector: choose the kit, choose the size, add to cart. A single-item option can exist as a secondary link, not as the default.</p>
<p><strong>The bundle is an upgrade.</strong> The shopper landed on a single SKU. Under the add-to-cart button, one block: &quot;Make it a set — save 12%.&quot; Still on the first screen on desktop. Still before reviews on mobile.</p>
<p><strong>The bundle is discovery.</strong> Someone might want a different kit entirely. That belongs in a collection or a &quot;complete the look&quot; after the decision is made — never as the only offer.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/addly/icon.png" alt="Addly: AI Bundles app &amp; Upsell app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Addly: AI Bundles app &amp; Upsell</p>
<p class="appcta__text">Product bundles, quantity breaks, volume discounts and AI-recommended offers for Shopify.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/addly?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="what-above-the-fold-actually-means">What above the fold actually means</h2>
<p>On a 1440px desktop, the buy box and the first 200px under it. On a phone, whatever is visible before the first scroll. If your bundle widget is in a tab labelled &quot;Bundles&quot; next to &quot;Reviews&quot; and &quot;Shipping&quot;, you have hidden it.</p>
<p>Apps that inject a block &quot;somewhere in the product template&quot; often land in the somewhere. Open the theme editor and put the block where the job is.</p>
<h2 id="do-not-double-place">Do not double-place</h2>
<p>Showing the same kit in the buy box and again in a carousel under the description does not increase take rate. It increases banner blindness. Pick the slot that matches the job. Delete the other.</p>
<h2 id="after-they-add">After they add</h2>
<p>The cart can offer a <em>different</em> completion — a complementary SKU, a quantity break, a free-shipping nudge. It should not repeat the kit they just declined or just accepted. Repeating it is how offers start to feel like pop-ups that never close.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Should a bundle have its own product page?</summary><div class="faq__a"><p>If it is a kit you want to advertise, yes. The product page of a component can still offer the kit as an upgrade, but the kit needs a URL if you want it in ads, search and collections.</p></div></details>
<details><summary>Does a bundle selector hurt single-item conversion?</summary><div class="faq__a"><p>It can, if it replaces the simple add-to-cart. Keep &quot;Buy this item&quot; as the default and &quot;Make it a set&quot; as the second action, not the other way around — unless the set is genuinely the product.</p></div></details>
<details><summary>What about mobile?</summary><div class="faq__a"><p>On mobile, &quot;above the fold&quot; is one screen. Put the offer in the buy box or the first block under it. Anything that requires a scroll past reviews will not be seen before add-to-cart.</p></div></details>
<details><summary>Can I put bundles in the cart instead?</summary><div class="faq__a"><p>Yes, as a second chance. Do not rely on the cart as the first chance. Most shoppers who wanted the kit would have taken it on the product page if they had seen it.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Variant-Level Volume Discounts — Clear Slow Shopify Stock Without a Sitewide Sale</title>
      <link>https://ninety9.dev/blog/variant-volume-discounts-clear-stock.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/variant-volume-discounts-clear-stock.html</guid>
      <pubDate>Wed, 08 Jul 2026 09:00:00 GMT</pubDate>
      <category>Bundles &amp; Upsells</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>How to put quantity breaks on specific variants so you move the colour that is not selling, without discounting the whole product line.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>A sitewide sale discounts the SKUs that were going to sell anyway. A variant-level break discounts only the ones that are stuck.</li><li>Target the break at the variant, not the product. &quot;Navy / M&quot; can be 3 for 10% while &quot;Black / M&quot; stays full price.</li><li>Show the break on the product page only when the selected variant qualifies. A global table that does not apply is a trust leak.</li><li>Pair slow-moving variants with a bundle or add-on only after the break exists. Discount stacking is how clearance becomes a loss.</li></ul></section>
<p>Clearance is usually a blunt instrument. You take 20% off a product because one colour is stuck, and you also take 20% off the colour that was going to sell at full price by Friday.</p>
<p>Variant-level volume discounts are the sharp instrument. The stuck SKU gets a reason to buy two. The healthy SKU is left alone.</p>
<h2 id="when-this-is-the-right-tool">When this is the right tool</h2>
<p>Use it when:</p>
<ul>
<li>One colourway or size is overstocked and the rest of the product is fine.</li>
<li>The product is a consumable or a multi-buy category (socks, candles, supplements, basics) where taking two is reasonable.</li>
<li>You would rather move units than run a sitewide percentage off.</li>
</ul>
<p>Do not use it when the variant is simply mispriced, or when the product is a single-purchase item (a sofa, a laptop). A quantity break on a sofa is a joke.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/addly/icon.png" alt="Addly: AI Bundles app &amp; Upsell app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Addly: AI Bundles app &amp; Upsell</p>
<p class="appcta__text">Product bundles, quantity breaks, volume discounts and AI-recommended offers for Shopify.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/addly?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="how-to-present-it">How to present it</h2>
<p>The shopper selects a variant. If that variant has a break, a short table appears: &quot;Buy 2, save 10%. Buy 4, save 20%.&quot; If they switch to a variant without a break, the table disappears.</p>
<p>A table that stays on screen for variants it does not apply to is a lie. Lies in the buy box are expensive.</p>
<h2 id="margin-not-just-sell-through">Margin, not just sell-through</h2>
<p>Work the break backwards from the landed cost of the stuck variant. If you can give 10% at two units and still sit above your floor, that is the offer. If you cannot, markdown a single unit instead — a quantity break that loses money faster is not merchandising.</p>
<p>Do not stack a collection sale on top. The whole point of targeting the variant is to <em>avoid</em> a second discount.</p>
<h2 id="inventory-after-it-works">Inventory after it works</h2>
<p>When a colourway starts moving, turn the break off. A quantity discount that outlives the overstock becomes the new regular price, and you will train your best customers to wait for it.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Will customers get angry that one colour is cheaper?</summary><div class="faq__a"><p>Rarely, if the cheaper colour is clearly a specific variant and not a secret. Shoppers understand clearance. They do not understand a price that changes for no visible reason.</p></div></details>
<details><summary>Should I use a quantity break or a markdown?</summary><div class="faq__a"><p>A markdown is for &quot;we will never restock this.&quot; A quantity break is for &quot;we would like you to take more than one.&quot; If the variant is a dead colourway you will not reorder, markdown it. If it is a size curve problem, use a break.</p></div></details>
<details><summary>Can I combine this with a collection sale?</summary><div class="faq__a"><p>You can, and you probably should not. Two discounts on the same line make margin unreadable. Pick one mechanism per variant.</p></div></details>
<details><summary>Where do I show the offer?</summary><div class="faq__a"><p>Under the variant picker, as a small table that updates when the shopper changes colour or size. Not in a banner that applies to the whole product.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>AI Product Offers on Shopify — Use Purchase History, Not a Guessing Engine</title>
      <link>https://ninety9.dev/blog/ai-offers-from-purchase-history.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/ai-offers-from-purchase-history.html</guid>
      <pubDate>Tue, 07 Jul 2026 09:00:00 GMT</pubDate>
      <category>Bundles &amp; Upsells</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>Why catalogue-based recommendations stall, how to build offers from real order history, and the guardrails that stop AI from suggesting a second pair of the same shoes.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>The only reliable training set for product offers is your own orders. Category-similarity engines recommend more of what is already in the cart.</li><li>Start with frequently bought together from order history, then let a model rank within that set. Do not let a model invent pairs you have never sold.</li><li>Exclude the parent product, its other variants, and anything already in the cart. Most &quot;AI&quot; failures are missing exclusions, not missing intelligence.</li><li>Show one or two offers. A model that returns twelve candidates is a merchandising problem, not a UI problem.</li></ul></section>
<p>&quot;AI offers&quot; is a vague promise. The useful version is narrow: look at what people actually bought together, then show the next most likely SKU at the moment the shopper is about to buy.</p>
<p>The useless version is a generic similarity model pointed at your catalogue. That model will recommend another pair of running shoes to someone who just added running shoes, because they are similar. Similar is not complementary.</p>
<h2 id="order-history-is-the-feature">Order history is the feature</h2>
<p>Co-occurrence is a boring statistic and a very good merchandiser. If 18% of orders that contain the ceramic mug also contain the 250g beans, that pair is an offer. You do not need a foundation model to notice it. You need a query and a place to put the result.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/addly/icon.png" alt="Addly: AI Bundles app &amp; Upsell app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Addly: AI Bundles app &amp; Upsell</p>
<p class="appcta__text">Product bundles, quantity breaks, volume discounts and AI-recommended offers for Shopify.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/addly?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<p>A model becomes useful one layer up: ranking which of several historically real pairs to show <em>this</em> shopper, given what is already in the cart, the market they are in, and the margin you will accept. Ranking is AI. Inventing pairs you have never sold is improvisation.</p>
<h2 id="guardrails-that-prevent-the-obvious-failures">Guardrails that prevent the obvious failures</h2>
<p>Before any model runs, exclude:</p>
<ul>
<li>The product already on the page</li>
<li>Other variants of that product</li>
<li>Anything already in the cart</li>
<li>Out-of-stock SKUs</li>
<li>SKUs you have blacklisted (clearance you do not want attached to full-price heroes, or vice versa)</li>
</ul>
<p>If your AI offer still looks stupid, the exclusions are incomplete. Fix those before you tune the model.</p>
<h2 id="one-slot-two-products-at-most">One slot, two products at most</h2>
<p>The product page can support a single &quot;complete this&quot; block. Two products in that block is plenty. Twelve is a catalogue dump with nicer copy.</p>
<p>If the model returns a long tail of weak pairs, you do not need a carousel. You need a higher threshold: only show a pair that has actually occurred, at a rate you would defend to a merchandiser.</p>
<h2 id="measure-the-pair-not-the-click">Measure the pair, not the click</h2>
<p>Clicks on a recommendation are vanity. Attach rate and the margin of the attached SKU are the job. An AI that attaches a low-margin accessory at 8% is worse than a merchandiser who attaches a high-margin one at 5%.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Do I need a lot of orders before AI offers work?</summary><div class="faq__a"><p>You need enough co-occurrence. For a catalogue of a few hundred SKUs, a few thousand orders with two or more line items is usually enough to beat a manual merchandiser. Below that, curate the pairs yourself and keep the AI off.</p></div></details>
<details><summary>Can AI replace a merchandiser?</summary><div class="faq__a"><p>It can replace the spreadsheet of &quot;what goes with what&quot; once the data exists. It cannot replace a decision about what you are willing to discount, or which products you refuse to attach to which.</p></div></details>
<details><summary>Why does my AI keep recommending the same product?</summary><div class="faq__a"><p>Because the cart already contains something in that category and the model is doing category similarity. Switch the source to order co-occurrence and add an exclusion for the parent and its variants.</p></div></details>
<details><summary>Should AI offers be on the product page or in the cart?</summary><div class="faq__a"><p>Both, with different jobs. On the product page, complete this product. In the cart, complete this order. Do not show the same pair in both places in the same session.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>The Add-to-Cart Moment — Why It Is the Best Upsell Slot You Own</title>
      <link>https://ninety9.dev/blog/add-to-cart-popup-upsell.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/add-to-cart-popup-upsell.html</guid>
      <pubDate>Tue, 07 Jul 2026 09:00:00 GMT</pubDate>
      <category>CRO &amp; Analytics</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>The instant after a shopper commits is the highest-intent moment in the session. Here is how to use it without breaking the flow you just earned.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>The add-to-cart click is a commitment signal. Offers shown after it do not risk the primary conversion because the primary conversion already happened.</li><li>Confirm before you sell. A popup that does not first acknowledge the item was added creates doubt at the exact moment you wanted certainty.</li><li>One offer, priced at fifteen to forty percent of the anchor item, with a one-click accept and an obvious decline.</li><li>Take rate is the fast signal for relevance. Attach revenue is the slow signal for whether the slot is worth using at all.</li></ul></section>
<p>There is one moment in an ecommerce session where the shopper has unambiguously committed and has not yet moved on to the next thing. It lasts about a second and a half, and it starts the instant they click &quot;add to cart&quot;.</p>
<p>Most stores fill it with a small toast notification in the corner of the screen.</p>
<h2 id="why-this-moment-is-different">Why this moment is different</h2>
<p>Every upsell placement is a trade between attention and risk. The product page has attention but any offer there competes with the buy button. The checkout has commitment but you cannot touch it. The confirmation page has zero risk but the shopper is mentally finished.</p>
<p>The add-to-cart moment has an unusual combination:</p>
<ul>
<li><strong>Commitment has already happened.</strong> The add is done. You cannot lose it.</li>
<li><strong>Attention is at its peak.</strong> The shopper just performed a deliberate action and is watching for the result.</li>
<li><strong>The purchase frame is active.</strong> They are in buying mode, not browsing mode, and the difference in receptiveness is large.</li>
<li><strong>No friction has been added yet.</strong> They have not entered an address or a card number, so an additional item costs them nothing procedurally.</li>
</ul>
<p>That combination does not exist anywhere else in the session.</p>
<h2 id="confirm-first-then-offer">Confirm first, then offer</h2>
<p>The most important design rule, and the one that separates a popup that works from one that damages the funnel.</p>
<p>The shopper clicked a button and is waiting to find out what happened. If the first thing they see is an offer for a different product, their immediate question is &quot;did my item get added?&quot; — and you have replaced certainty with doubt at exactly the wrong instant.</p>
<p>The correct sequence:</p>
<ol>
<li><strong>Confirm.</strong> Show the item added — image, name, variant, quantity. Unambiguous.</li>
<li><strong>Then offer.</strong> One complementary item, clearly secondary to the confirmation.</li>
<li><strong>Then two exits.</strong> Continue shopping, and go to cart / checkout.</li>
</ol>
<p>The confirmation is not a formality. It is what buys you the right to the rest of the modal.</p>
<aside class="callout callout--key">
<p class="callout__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 7a5 5 0 1 0-4.5 5H12v3h3v3h4v-4l-4-4V7Z"/></svg>The two-second rule</p>
<p>Someone glancing at your popup for two seconds should be able to answer &quot;did it work?&quot; without reading. If the answer requires parsing an offer first, the layout is wrong.</p>
</aside>
<h2 id="choosing-the-offer">Choosing the offer</h2>
<p>The same relevance rules that govern any cross-sell apply, with one addition: the offer must require <em>no new evaluation</em>.</p>
<p>The shopper has just finished deciding. Asking them to start a fresh decision — a different category, an unfamiliar product, a considered purchase — breaks the momentum you were trying to use.</p>
<p>What works:</p>
<ul>
<li><strong>A direct accessory.</strong> Case, filter, refill, mount, strap. Obvious, cheap relative to the anchor, one-word justification.</li>
<li><strong>A quantity upgrade on the same item.</strong> &quot;Add a second and save 10%.&quot; Zero new evaluation, and consistently the highest take rate of any offer here.</li>
<li><strong>The completion item.</strong> The third thing in a set where they now have two.</li>
<li><strong>A consumable that pairs with it.</strong> Coffee with a grinder, blades with a razor.</li>
</ul>
<p>What does not:</p>
<ul>
<li><strong>An alternative to what they just added.</strong> Reopens a closed decision.</li>
<li><strong>Anything more than half the anchor price.</strong> Becomes a second purchase decision, gets deferred.</li>
<li><strong>A category they have not shown interest in.</strong> The intent signal is about <em>this</em> product.</li>
<li><strong>Multiple options.</strong> Two choices halve the clarity. One offer, take it or leave it.</li>
</ul>
<p>The price ratio is worth restating because it is the most reliable predictor: 15–40% of the anchor product&#39;s price is the zone where an accessory reads as obvious.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/monet/icon.png" alt="Monet • AI Popup Bundle Addons app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Monet • AI Popup Bundle Addons</p>
<p class="appcta__text">AI upsell popups triggered on add to cart, checkout initiation and exit intent.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/monet?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="design-specifics">Design specifics</h2>
<ul>
<li><strong>One-click accept.</strong> Accepting must add the item and update the cart with no further steps. Any additional step loses a large share of the people who said yes.</li>
<li><strong>An obvious decline.</strong> &quot;No thanks&quot; as visible text, not a grey X in a corner. A hidden dismiss control converts a helpful moment into a hostile one.</li>
<li><strong>Never block the checkout path.</strong> Both exits — continue shopping and go to checkout — must be visible without scrolling, on mobile.</li>
<li><strong>Show the price and the saving.</strong> If there is a discount for taking it now, state both the price and the amount saved.</li>
<li><strong>Keep it small.</strong> This is not a landing page. Confirmation, one product, two buttons.</li>
<li><strong>Auto-dismiss is optional and risky.</strong> A modal that vanishes mid-read is worse than one that waits.</li>
</ul>
<h2 id="frequency">Frequency</h2>
<p>Cap it. This is the difference between a tool and an irritation.</p>
<p>Reasonable defaults:</p>
<ul>
<li><strong>Once per session.</strong> A shopper adding five items should see the offer once, not five times.</li>
<li><strong>Never twice for the same anchor product.</strong></li>
<li><strong>Suppress after a decline.</strong> Someone who said no has answered the question.</li>
<li><strong>Suppress in checkout.</strong> Once they have started checkout, stop.</li>
</ul>
<p>The failure mode here is well known to anyone who has shopped online: the store where every single add triggers a modal. Shoppers respond by adding fewer items, which is the exact opposite of the goal.</p>
<h2 id="popup-or-cart-drawer">Popup or cart drawer?</h2>
<p>Both surfaces can carry this offer, and they behave differently.</p>
<div class="tablewrap"><table><thead><tr><th></th><th>Add-to-cart popup</th><th>Cart drawer upsell</th></tr></thead><tbody>
<tr><td>Attention</td><td>Full, undivided</td><td>Shared with totals and checkout</td></tr>
<tr><td>Take rate per impression</td><td>Higher</td><td>Lower</td></tr>
<tr><td>Perceived intrusiveness</td><td>Higher</td><td>Lower</td></tr>
<tr><td>Risk to checkout flow</td><td>Small but real</td><td>Minimal</td></tr>
<tr><td>Best for</td><td>One high-value offer</td><td>Ongoing, ambient suggestion</td></tr>
</tbody></table></div>
<p>Most stores should run one, not both. Running both means the same shopper sees a related-product suggestion twice in ten seconds, which reads as pressure and tends to lower the take rate of each.</p>
<p>If you want to combine them, differentiate the jobs: popup for a quantity upgrade on the item just added, drawer for the threshold progress bar and a complementary accessory. Different offers, different purposes.</p>
<aside class="callout callout--warn">
<p class="callout__title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/></svg>Watch the mobile viewport</p>
<p>A modal that occupies the full screen on a phone and pushes the &quot;go to cart&quot; button below the fold is the single most common way this pattern goes wrong. Test on a small device with the keyboard closed and the browser chrome visible.</p>
</aside>
<h2 id="measuring-it">Measuring it</h2>
<p>Two fast metrics and two slow ones.</p>
<p><strong>Fast — tells you about relevance, within days:</strong></p>
<ul>
<li><strong>Take rate</strong> — accepts ÷ impressions. If this is near zero, the offer is irrelevant and no amount of design will fix it.</li>
<li><strong>Dismiss speed.</strong> If most people close within a second, they are not reading it.</li>
</ul>
<p><strong>Slow — tells you whether the slot is worth using, over weeks:</strong></p>
<ul>
<li><strong>Attach revenue per impression.</strong> The commercial number.</li>
<li><strong>Cart-to-checkout rate</strong>, before and after. The guardrail.</li>
<li><strong>Adds per session.</strong> The subtle one. If shoppers add fewer items overall after you launch the popup, it is discouraging the behaviour you want, and the attach revenue is not compensating for the loss.</li>
</ul>
<p>That last metric catches the frequency problem before your reviews do.</p>
<h2 id="a-starting-configuration">A starting configuration</h2>
<ul>
<li>Fires on add to cart, once per session, desktop and mobile.</li>
<li>Confirmation block first, offer second, two exits third.</li>
<li>Offer: one complementary item at 15–40% of the anchor price, drawn from real order-history pairings.</li>
<li>Small discount for accepting immediately, in the 10–15% range.</li>
<li>Suppressed after a decline, and suppressed entirely in checkout.</li>
</ul>
<p>Then read the take rate after a week. If it is close to zero, the problem is the pairing, not the popup.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Does an add-to-cart popup hurt conversion rate?</summary><div class="faq__a"><p>It should not, because it appears after the shopper has already added the item. The risk is not losing the add but losing the checkout, which happens when the popup obscures the path forward, requires a decision to dismiss, or makes the shopper doubt whether their item was actually added.</p></div></details>
<details><summary>What is the difference between an add-to-cart popup and a cart drawer upsell?</summary><div class="faq__a"><p>The popup is a single dedicated moment with one offer and the shopper&#39;s full attention. The cart drawer upsell sits alongside line items, totals and a checkout button, competing for attention. The popup converts better per impression, the drawer is less intrusive. Many stores run one or the other rather than both.</p></div></details>
<details><summary>What should the offer be?</summary><div class="faq__a"><p>A complementary item priced well below the product just added, ideally between fifteen and forty percent of its price. It should require no new evaluation - an accessory, a consumable that pairs with it, a quantity upgrade on the same item.</p></div></details>
<details><summary>Should the popup appear on every add to cart?</summary><div class="faq__a"><p>No. Cap it to once per session at most. A popup on every add is the fastest way to make a shopper stop adding items, and it converts a helpful moment into an obstacle.</p></div></details>
<details><summary>Is a popup better than expanding the cart drawer?</summary><div class="faq__a"><p>For a single high-value offer, generally yes, because the popup has no competition for attention. For stores that want the offer to feel like part of the cart rather than an interruption, the drawer is safer. Test both against cart-to-checkout rate, not just against attach rate.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Product Add-ons vs Bundles on Shopify — Pick the Offer That Fits the SKU</title>
      <link>https://ninety9.dev/blog/product-addons-vs-bundles.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/product-addons-vs-bundles.html</guid>
      <pubDate>Mon, 06 Jul 2026 09:00:00 GMT</pubDate>
      <category>Bundles &amp; Upsells</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>When a product add-on converts better than a bundle, when a bundle is the only honest structure, and how to avoid stacking both on the same product page.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>Add-ons are optional extras attached to a product the shopper already wants. Bundles are a new product made of several SKUs.</li><li>Use add-ons when the extra is small, optional and would look silly as its own PDP (gift wrap, a spare cable, extended warranty).</li><li>Use bundles when the combination is the point — a routine, a kit, a &quot;complete the look&quot; that you would be happy to stock as its own SKU.</li><li>Do not run both on the same product page. Two offer types compete for the same increment and both convert worse.</li></ul></section>
<p>A bundle says &quot;this is the product&quot;. An add-on says &quot;this product, plus a small yes&quot;. Mixing them on one page asks the shopper to solve two different problems at once. Most of them solve neither.</p>
<h2 id="the-test">The test</h2>
<p>Would you be willing to give the combination its own URL, its own photos and its own inventory? If yes, it is a bundle. If no — if the extra only makes sense attached to something else — it is an add-on.</p>
<p>Gift wrap is an add-on. A skincare routine of cleanser, serum and moisturiser is a bundle. A spare charging cable next to a lamp is an add-on. A &quot;desk kit&quot; of lamp, cable and bulb sold as one price is a bundle.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/addly/icon.png" alt="Addly: AI Bundles app &amp; Upsell app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Addly: AI Bundles app &amp; Upsell</p>
<p class="appcta__text">Product bundles, quantity breaks, volume discounts and AI-recommended offers for Shopify.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/addly?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="why-add-ons-convert-on-the-product-page">Why add-ons convert on the product page</h2>
<p>The shopper has already chosen the parent. The remaining question is small. A checkbox is the right UI: low commitment, high clarity, no new product to evaluate.</p>
<p>That is also why add-ons fail when they look like products. A second product card with a price, a review stars row and a &quot;learn more&quot; link is a detour. Detours get skipped.</p>
<h2 id="why-bundles-belong-in-the-catalogue">Why bundles belong in the catalogue</h2>
<p>A bundle has a job the add-on does not: it can be found. It can rank. It can be advertised. It can have a margin structure you planned, rather than a last-second checkbox.</p>
<p>If 40% of buyers of A also take B, stop hiding B as an add-on and make A+B a product. You will sell it to people who never visited A&#39;s page.</p>
<h2 id="do-not-stack">Do not stack</h2>
<p>The product page can carry one incremental offer besides the buy box. If you already have a bundle selector, do not also drop three add-on checkboxes. If you already have add-ons, do not also run a &quot;frequently bought together&quot; strip that repeats the same SKUs.</p>
<p>Pick the structure that matches the SKU. Kill the other one. <a href="/blog/how-to-increase-average-order-value-shopify.html">Average order value</a> goes up when the page asks one extra question, not four.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Is a frequently-bought-together widget a bundle or an add-on?</summary><div class="faq__a"><p>It behaves like an add-on — the shopper can take or leave the extra SKUs — even if you discount it like a bundle. Treat it as an add-on in the page layout: after the buy box, not instead of it.</p></div></details>
<details><summary>Should add-ons be discounted?</summary><div class="faq__a"><p>Rarely. The shopper already wants the parent product. Discounting the extra trains them to wait for a deal on something they would have added at full price.</p></div></details>
<details><summary>Can I turn a popular add-on into a bundle later?</summary><div class="faq__a"><p>Yes, and you should once a pair starts selling together more often than not. At that point the combination is the product, and a bundle price is honest.</p></div></details>
<details><summary>Where do add-ons go on the page?</summary><div class="faq__a"><p>Directly under the add-to-cart button, as a checklist, not in a related-products carousel at the footer. Footer carousels are for browsing. Add-ons are for this purchase.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>FOMO in the Shopify Cart — Announcement Bars That Help Instead of Nag</title>
      <link>https://ninety9.dev/blog/fomo-blocks-in-the-cart-drawer.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/fomo-blocks-in-the-cart-drawer.html</guid>
      <pubDate>Sun, 05 Jul 2026 09:00:00 GMT</pubDate>
      <category>Cart &amp; Checkout</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>Which scarcity and urgency blocks belong in a cart drawer, which ones train shoppers to ignore you, and how to write a bar that is actually true.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>One true urgency signal beats three fake ones. &quot;12 sold in the last 24 hours&quot; only works if the number is real.</li><li>Countdown timers that reset on refresh are a trust leak. If you cannot honour the deadline, do not show a clock.</li><li>Put FOMO above the line items or on a single announcement bar — not on every product card.</li><li>Social proof in the cart should be about this order (stock, delivery, recent sales), not about the brand in general.</li></ul></section>
<p>Fear of missing out is not a design style. It is a claim about the world. If the claim is true, it belongs in the cart — the shopper is already committed enough to have added something, and a true constraint can finish the job. If the claim is fake, it belongs in the bin, because this is the last moment of trust before payment.</p>
<h2 id="the-only-fomo-that-belongs-in-a-cart">The only FOMO that belongs in a cart</h2>
<p>Three categories earn their pixel:</p>
<ol>
<li><strong>Real inventory.</strong> Variant-level stock, updated, not a theme setting of &quot;3&quot;.</li>
<li><strong>Real promotions.</strong> A sale that actually ends at a timestamp you will not quietly extend.</li>
<li><strong>Real delivery constraints.</strong> Cut-off times for next-day dispatch that your warehouse actually keeps.</li>
</ol>
<p>Everything else — &quot;27 people are looking at this&quot;, &quot;in high demand&quot;, a timer that restarts at 15:00 every time you open the tab — is theatre. Theatre converts once and then trains your regulars to ignore you.</p>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/addy/icon.png" alt="Addy: AI Cart Drawer &amp; Bundles app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Addy: AI Cart Drawer &amp; Bundles</p>
<p class="appcta__text">AI cart drawer with in-cart upsells, volume bundles, free shipping bars and per-Market targeting.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/addy-cart-drawer?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<h2 id="one-bar-not-a-stack">One bar, not a stack</h2>
<p>Announcement bars compound badly. A free-shipping progress bar is a goal. A countdown is a deadline. A stock warning is a constraint. Pick the one that is actually true <em>for this basket</em> and silence the others.</p>
<p>A useful default:</p>
<ul>
<li>Progress bar if they are under a threshold you care about.</li>
<li>Delivery cut-off if they are over the threshold and you can still make a same-day dispatch.</li>
<li>Stock only on the line item that is actually scarce, not as a global banner.</li>
</ul>
<h2 id="writing-the-line">Writing the line</h2>
<p>Specific and boring beats vague and exciting.</p>
<ul>
<li>Bad: &quot;Hurry, selling fast!&quot;</li>
<li>Good: &quot;Order in the next 2 hours for dispatch today.&quot;</li>
<li>Bad: &quot;Limited stock.&quot;</li>
<li>Good: &quot;Only 2 left in Navy / M.&quot;</li>
</ul>
<p>If you cannot write the good version because the data is not there, you do not have a FOMO feature. You have a copy box.</p>
<h2 id="trust-is-an-aov-feature">Trust is an AOV feature</h2>
<p>Shoppers who believe your cart will believe an upsell. Shoppers who have been burned by a fake timer will not. The quietest stores often have the highest take rates on in-cart offers, because nothing in the drawer has given them a reason to doubt the next sentence.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Do countdown timers increase conversions in the cart?</summary><div class="faq__a"><p>Real ones can, for genuine promotions with a real end time. Timers that reset when you reload the page train shoppers to distrust every other claim in the store, including the ones that are true.</p></div></details>
<details><summary>How many FOMO blocks should a cart have?</summary><div class="faq__a"><p>One. A bar or a single line of stock/sales proof. A drawer that shouts in three places is a drawer shoppers learn to skim.</p></div></details>
<details><summary>Is &quot;only 3 left&quot; okay if it is true?</summary><div class="faq__a"><p>Yes, if it is true at the variant level and updates. Inventory theatre (&quot;only 3 left&quot; on a SKU with 400 units) is worse than silence.</p></div></details>
<details><summary>Should FOMO sit above or below the upsell?</summary><div class="faq__a"><p>Above the line items. It frames the basket. Below the upsell it reads as a caption on an offer, which is a different and weaker job.</p></div></details>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Cart Drawer Performance — Keep Shopify LCP Intact After You Add Upsells</title>
      <link>https://ninety9.dev/blog/cart-drawer-performance-lcp.html</link>
      <guid isPermaLink="true">https://ninety9.dev/blog/cart-drawer-performance-lcp.html</guid>
      <pubDate>Sat, 04 Jul 2026 09:00:00 GMT</pubDate>
      <category>CRO &amp; Analytics</category>
      <dc:creator>Ninety9 Team</dc:creator>
      <description>How a slide cart can wreck Largest Contentful Paint, the loading pattern that does not, and the checks to run before and after you install a cart app.</description>
      <content:encoded><![CDATA[<section class="takeaways"><h2>Key takeaways</h2><ul><li>A cart drawer should never appear in the initial HTML of the product page. If it does, it is in the LCP path whether you can see it or not.</li><li>Load the app as a theme app extension, asynchronously, and render the drawer on first open — not on first paint.</li><li>Images in the drawer (product thumbs, upsell cards) must be lazy. They are off-screen until the shopper adds something.</li><li>Measure LCP on a product page with the app on and off. If it moved, the drawer is doing work too early.</li></ul></section>
<p>A cart drawer that costs you 200ms of Largest Contentful Paint is not a conversion feature. It is a tax on every session, including the ones that never open the cart.</p>
<p>The rule is simple: the drawer does not exist until someone needs it. Everything else is implementation detail.</p>
<h2 id="where-drawers-show-up-in-the-critical-path">Where drawers show up in the critical path</h2>
<p>Three patterns show up in real stores:</p>
<ol>
<li><strong>The HTML is in the page.</strong> The drawer markup is in the initial response, hidden with CSS. Images inside it still decode. Fonts still swap. LCP still pays.</li>
<li><strong>The JavaScript is in the page.</strong> A 150KB bundle parses on every product page so that a drawer <em>could</em> open. Parse time is main-thread time.</li>
<li><strong>The app injects into theme.liquid.</strong> This is the legacy pattern. It is also the one Shopify has spent years trying to kill, for good reason.</li>
</ol>
<aside class="appcta not-prose">
<img class="appcta__icon appicon" src="/assets/img/apps/addy/icon.png" alt="Addy: AI Cart Drawer &amp; Bundles app icon" width="56" height="56" loading="lazy" decoding="async">
<div class="appcta__body">
<p class="appcta__name">Addy: AI Cart Drawer &amp; Bundles</p>
<p class="appcta__text">AI cart drawer with in-cart upsells, volume bundles, free shipping bars and per-Market targeting.</p>
</div>
<a class="btn btn--accent btn--sm appcta__btn" href="https://apps.shopify.com/addy-cart-drawer?src=ninety9dev" target="_blank" rel="noopener">Install on Shopify<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</aside>
<p>The pattern that does not show up in LCP: a theme app extension, loaded asynchronously, that fetches drawer UI on first open and caches it for the session.</p>
<h2 id="what-to-measure">What to measure</h2>
<p>Before you install anything, record on a representative product page:</p>
<ul>
<li>Largest Contentful Paint</li>
<li>Total blocking time</li>
<li>Transfer size of JS on load</li>
</ul>
<p>Install the app. Repeat the same page, same device, same throttling. If LCP moved by more than a rounding error, the drawer is doing work at the wrong time.</p>
<p>Do not trust a homepage test. Homepages often have a hero image that masks a JS regression. Product pages are where carts actually live.</p>
<h2 id="images-and-upsells">Images and upsells</h2>
<p>Every upsell card is an image request. If those requests start on page load, you have built a carousel the shopper did not ask for. Lazy-load thumbs. Decode on open. If the first open takes 200ms to paint, that is acceptable; it is a click later, not a tax on landing.</p>
<h2 id="the-fail-safe">The fail-safe</h2>
<p>If the app is unreachable, the drawer should not render and the theme&#39;s native add-to-cart should still work. A conversion widget that can take down add-to-cart is worse than no widget. Fail safe, always.</p>
<h2 id="faq">Frequently asked questions</h2>
<div class="faq">
<details><summary>Will a cart drawer app slow my store down?</summary><div class="faq__a"><p>It should not. A well-built drawer loads asynchronously, renders only when opened, and never blocks the initial paint. If your Largest Contentful Paint changes after install, the app is in the critical path and you should replace it.</p></div></details>
<details><summary>Does Shopify&#39;s theme app extension model guarantee speed?</summary><div class="faq__a"><p>It removes the worst failure mode — a script injected into theme.liquid that runs on every page. It does not stop an app from downloading a large bundle on load. Check the network panel.</p></div></details>
<details><summary>Should I preload the drawer?</summary><div class="faq__a"><p>No. Preloading a UI the shopper has not opened yet is paying for work you may never need. Fetch on first add-to-cart, then keep it warm for the rest of the session.</p></div></details>
<details><summary>What about app pixels and analytics in the cart?</summary><div class="faq__a"><p>Fire them on open or on offer impression, not on page load. Cart-level analytics that run on every product page are a common source of main-thread work that nobody asked for.</p></div></details>
</div>]]></content:encoded>
    </item>
  </channel>
</rss>
