Basket Completion: A Data-Driven Approach to Smarter E-Commerce Upsells

19 August 2026

18 minute read

Most upsell systems start with a simple question:

What product should we recommend to this customer?

There is often a better one:

What product is missing from this customer's basket?

The difference sounds small, but it changes how recommendations work. Instead of guessing what a shopper might like in general, we can use historical orders to learn which products naturally belong together.

If thousands of customers who bought A and B also bought C, a shopper currently holding A and B is a strong candidate for C. That is the core of a Basket Completion Model: recommendations grounded in real purchase behavior rather than manual rules, product similarity, or generic bestsellers.

The basic idea

Imagine a Shopify catalog with three products: A, B, and C. Historical orders show that the combination A + B + C appears especially often.

That alone says the three products travel together. More useful is how we decompose that basket into incomplete ones:

Existing basketMissing product
A + BC
A + CB
B + CA

Each row asks a different question: of customers who already had this subset, how often did they also buy the missing item? Direction matters - we are not storing “these products are related,” we are storing completion rules.

1. Start with historical orders

Treat every order as a set of products. Quantities can usually be ignored at first: two units of A and one of B still become {A, B}.

Example history:

OrderProducts
#1A
#2A, B
#3A, B, C
#4A, B, C
#5A, B, D
#6A, C
#7A, B, C
#8B, C
#9A, C, D
#10A, B, C

The signal we care about is co-occurrence: which products appeared in the same basket.

2. Break baskets into smaller combinations

A three-product order {A, B, C} yields the two-product subsets {A, B}, {A, C}, and {B, C}, each with one missing completion candidate.

From one full basket we store three directed relationships:

  • Customers with A + B frequently add C
  • Customers with A + C frequently add B
  • Customers with B + C frequently add A

3. Count how often each basket is completed

Across hundreds of thousands of orders we might see:

Existing basketAdditional productOrders
A + BC4,000
A + BD1,200
A + BE300
A + CB4,000
A + CD500
B + CA4,000

Raw counts already suggest recommending C for A + B - but frequency alone is not enough.

4. Calculate the probability of the missing product

Suppose A + B + C appears in 4,000 orders. That sounds strong until we learn A + B appears in 1,000,000 orders - then C only completes 0.4% of those baskets.

What we actually want is the conditional probability (confidence in association-rule terms):

P(CA,B)=Orders containing A,B,COrders containing A,BP(C \mid A,B) = \frac{\text{Orders containing } A,B,C}{\text{Orders containing } A,B}

If 10,000 orders contain A + B and 4,000 contain A + B + C:

P(CA,B)=400010000=0.40=40%P(C \mid A,B) = \frac{4000}{10000} = 0.40 = 40\%

In plain language: 40% of historical A + B baskets also contained C.

Conditional probability for A + B

Of historical orders that contained A and B, how often did each candidate also appear?

5. Direction is extremely important

The same three products can produce very different recommendation strengths depending on what is already in the cart.

With 10,000 A + B orders and 4,000 A + B + C orders:

P(CA,B)=40%P(C \mid A,B) = 40\%

But if only 5,000 orders contain A + C (and the same 4,000 contain A + B + C):

P(BA,C)=80%P(B \mid A,C) = 80\%

So A + B → C at 40% is weaker than A + C → B at 80%, even though the cluster {A, B, C} is identical. The model must score basket-completion relationships, not undirected product clusters.

6. A practical Shopify example

A supplement store sells Protein Powder, Creatine, Shakers, Protein Bars, and T-Shirts.

Among 50,000 Protein Powder orders:

  • 30,000 also had a Shaker → 60%
  • 20,000 also had Creatine → 40%
  • 5,000 also had a T-Shirt → 10%

For a single-item cart of Protein Powder, the Shaker wins.

Go one level deeper: 20,000 orders contained Protein Powder + Creatine, and 18,000 of those also contained a Shaker:

P(ShakerProtein,Creatine)=90%P(\text{Shaker} \mid \text{Protein}, \text{Creatine}) = 90\%

Nobody configured “when someone buys powder and creatine, show this shaker.” The relationship was discovered from behavior - not bestsellers, not collection membership, not product similarity.

7. Why this differs from product similarity

Similarity engines recommend more of the same: more running shoes after running shoes. That helps discovery, but for upsells it can add decision friction.

Basket completion tends to surface complements:

  • Running shoes + socks → running belt
  • Camera + lens → memory card
  • Protein + creatine → shaker
  • Coffee machine → beans
  • Grill + meat → sauce

The system asks: what do customers with a basket like this usually buy that this customer does not have yet?

8. Use all orders, not only three-product baskets

A four-product order {A, B, C, D} contributes many rules at once: single-item implications, pair completions, and even A + B + C → D. Larger baskets should not be discarded - they densify the graph of completion rules.

9. More specific baskets usually produce better recommendations

For a cart with A + B we might see:

  • A → C at 25%
  • B → C at 20%
  • A + B → C at 65%

Knowing both A and B is present conveys more intent than either product alone. In production, prefer the most specific match with enough evidence, then fall back:

  1. Exact basket A + B + C → ?
  2. Pairs A + B, A + C, B + C
  3. Singles A, B, C
  4. Metadata / merchant defaults / bestsellers

Recommendation fallback hierarchy

Prefer exact basket matches, then broaden only when evidence is thin.

10. The problem of small sample sizes

A + B + C → D = 100% with support of 3 orders is less trustworthy than A + B → E = 72% with 15,000 orders.

Every rule needs at least two metrics:

Confidence - how often the candidate appears with the basket:

Confidence=Basket + candidate ordersBasket orders\text{Confidence} = \frac{\text{Basket + candidate orders}}{\text{Basket orders}}

Support - how many historical orders evidence the rule.

A hard minimum support threshold can drop tiny samples entirely. That helps, but it is blunt: a rule with 50 observations is treated the same as one with 50,000 once both clear the floor, and anything below the floor disappears even when it is directionally useful.

11. Use Bayesian smoothing instead of raw confidence

This is one of the biggest statistical upgrades for a basket-completion engine.

Imagine two candidates for the same basket A + B:

RuleEvidenceRaw confidence
A + B → C1 / 1100%
A + B → D8,000 / 10,00080%

Raw confidence picks C. Obviously D is the more trustworthy recommendation.

Instead of treating one lucky co-purchase as certainty, Bayesian smoothing pulls sparse estimates toward a prior until enough evidence accumulates.

With a Beta prior parameterized byα\alpha andβ\beta, the smoothed estimate is:

P^=successes+αobservations+α+β\hat{P} = \frac{\text{successes} + \alpha}{\text{observations} + \alpha + \beta}

Here successes are orders that contain the basket and the candidate; observations are all orders that contain the basket.

Choose a prior that reflects a conservative baseline - for example a weak prior centered near the store-wide purchase rate of the candidate, or a simple weakly informative prior such asα=2\alpha = 2,β=18\beta = 18 (mean 10%10\%). Then:

  • 1 / 1 is pulled from 100%100\% toward something like ⁣15%\sim\!15\% (or whatever the prior mean implies)
  • 8,000 / 10,000 stays close to 80%80\%

The sparse spike collapses; the well-supported rule barely moves.

Raw confidence vs Bayesian-smoothed estimate

A single co-purchase looks perfect as raw confidence; smoothing pulls it toward the prior while well-supported rules barely move.

That stability matters especially for smaller Shopify merchants, new products, and long-tail SKUs - the exact places where raw confidence is most overconfident. You can still keep a soft support floor, but smoothing means ranking no longer treats a single coincidence as a perfect score.

In the ranking formula from the next sections, prefer smoothed confidence over raw confidence:

Score=P^×log(1+Support)×Lift\text{Score} = \hat{P} \times \log(1 + \text{Support}) \times \text{Lift}

If 70% of A + B orders contain C, that looks excellent - unless C appears in 80% of all store orders. Then A + B is not making C more likely; C is simply popular.

Lift compares the conditional probability to the base rate:

Lift(A,BC)=P(CA,B)P(C)\text{Lift}(A,B \rightarrow C) = \frac{P(C \mid A,B)}{P(C)}

IfP(CA,B)=40%P(C \mid A,B) = 40\% andP(C)=10%P(C) = 10\%, thenLift=4\text{Lift} = 4: buyers of A + B are four times more likely than average to buy C.

IfP(CA,B)=70%P(C \mid A,B) = 70\% andP(C)=80%P(C) = 80\%, lift is 0.8750.875 - A + B actually makes C less likely than normal. Without lift, engines quietly degrade into “always recommend the bestseller.”

13. Ranking the candidates

For basket A + B we might have:

CandidateConfidenceSupportLift
C62%8,4003.2
D74%1801.8
E38%12,0004.1

No single column always wins. A practical score uses smoothed confidence and dampens weak support:

Score=P^×log(1+Support)×Lift\text{Score} = \hat{P} \times \log(1 + \text{Support}) \times \text{Lift}

The formula can later be tuned against live upsell conversion. The question shifts from “what appeared most often?” to “what has the strongest and most reliable relationship with this basket?”

Ranking candidates for basket A + B

Confidence alone is not enough - support and lift change which candidate should win.

14. Precompute the expensive part

Mining combinations across the order history is expensive. That work can run offline on a schedule. Store results as lookup rows:

BasketCandidateConfidenceSupportLift
AC25%12,0001.4
A + BC62%4,2003.2
A + CB74%3,9004.1

At cart or post-purchase time the runtime path is a keyed lookup for the current basket set, then ranking and filters - fast enough for storefront UX.

15. Business rules still matter

Probability should not ship unchecked. Before display, filter candidates:

  • Already in cart?
  • In stock / variant available?
  • Market and channel eligible?
  • Merchant exclusions?
  • Compatible with subscription / selling plan?
  • Price band acceptable?
  • Published and purchasable?

Keep two layers cleanly separated:

  • Recommendation layer - statistically best completion
  • Commerce layer - what may actually be offered

16. Cold start and thin data

Large shops may have millions of orders; small shops or new SKUs may have almost none. Use the fallback hierarchy from section 9 so behavioral data wins when it exists, without leaving empty states when it does not.

17. The model can continuously improve

A first version needs no ML - only orders, combinations, counters, confidence (preferably Bayesian-smoothed), support, lift, and ranking. Once live:

  • impressions, clicks, accept rate
  • conversion and incremental revenue
  • incremental gross margin
  • average upsell value

Historical co-purchase says customers often bought C with A + B. Live data says customers accepted C when offered. The second signal is the one to optimize toward over time.

18. Optimize expected value, not only probability

The most probable add-on is not always the best offer.

Suppose for A + B:

  • C converts at 25% with €3 incremental margin → expected €0.75
  • D converts at 15% with €15 incremental margin → expected €2.25
ExpectedValue=P(PurchaseBasket,Recommendation)×IncrementalValue\text{ExpectedValue} = P(\text{Purchase} \mid \text{Basket}, \text{Recommendation}) \times \text{IncrementalValue}

D wins on expected margin despite lower conversion.

Expected incremental margin

A lower conversion rate can still win if the incremental margin is high enough.

A mature engine evolves from “most likely completion” to “highest expected incremental value for this basket (and eventually this customer).”

From basket completion to an intelligent upsell engine

The approach can start almost naively:

  1. Turn historical Shopify orders into baskets
  2. Decompose baskets into combinations
  3. Count missing-product completions
  4. Compute confidence with Bayesian smoothing, plus support and lift
  5. Rank candidates and apply commerce filters
  6. Serve precomputed lookups at cart / post-purchase time
  7. Close the loop with live accept and margin data

A live cart {A, B} becomes a query:

{A,B}?\{A,B\} \rightarrow ?

and the historical graph may answer C because thousands of prior customers demonstrated:

If you are buying A and B, C is probably what you are missing.

That is a different philosophy from traditional “recommended products.” Instead of guessing what a shopper might like, the system learns how real customers complete their baskets - and the order history itself becomes the recommendation engine.

How this differs from Shopify’s native recommendations

There is some overlap with what Shopify already offers. One architectural difference still stands out.

What Shopify does

Shopify says its automatically generated related-product recommendations can use:

  • purchase history - products historically purchased together
  • similar product descriptions
  • related collections as a fallback

Shopify’s public API is fundamentally product-centric. You request recommendations for a particular productId or productHandle, and Shopify returns up to 10 recommendations.

Conceptually:

Shopify’s current developer documentation says related recommendations are auto-generated, while complementary recommendations must be manually configured through Search & Discovery.

So Shopify absolutely uses co-purchase information. Its documented storefront recommendation interface is not a general basket-completion API.

The basket is the input, not a single product

This model starts with the entire current basket.

That is a different problem from:

Consider this historical data:

  • A → C = 20%
  • B → C = 18%
  • A + B → C = 82%

Looking at A or B individually does not reveal the important relationship. The combination does.

The system captures the interactionP(CA,B)P(C \mid A,B) rather than onlyP(CA)P(C \mid A) orP(CB)P(C \mid B). That distinction is extremely valuable for upselling.

A concrete example

Imagine a supplement store. A customer has Whey Protein + Creatine.

Shopify’s product-level system might effectively have relationships like:

That is useful. Basket completion asks something more specific:

Historical data might show Whey + Creatine → Shaker with confidence 78%, support 12,430, and lift 3.7.

The recommendation is no longer merely “people who buy whey also buy shakers.” It is: customers purchasing this exact combination are 3.7× more likely than average to purchase a shaker. That is a stronger basket-level signal.

Where the architecture can go further

The full architecture described in this post goes considerably beyond what Shopify publicly documents:

CapabilityShopify nativeBasket completion engine
Purchase-history signalYesYes
Product similarityYesCould add
Collection relationshipsYesCould add
Single-product recommendationsYesYes
Multi-product basket matchingNot publicly documented as its recommendation API modelYes
A + B → C rulesNot exposed / documentedYes
ConfidenceNot exposedYes
SupportNot exposedYes
LiftNot exposedYes
Bayesian smoothingNot exposedCould add
Time decayNot exposedCould add
Promotion correctionNot exposedCould add
Margin optimizationNot exposedCould add
Upsell conversion learningNot exposed as ranking controlCould add
Exploration / banditsNot exposedCould add
Customer historyNot exposed in this APICould add

Be careful claiming Shopify does not internally use some of these techniques. Shopify does not publish the full internals of its recommendation algorithm, so we cannot know that.

The defensible statement is: Shopify does not publicly expose or document this level of basket-aware ranking and optimization.

The biggest opportunity is not beating Shopify at recommendations

Shopify’s native system is designed primarily around product discovery. Related-product documentation describes the goal as recommending products similar to the product the customer is interacting with.

Basket completion has a narrower objective: maximize incremental value from an upsell opportunity.

You do not necessarily care whether C is the “most relevant” product. You care whether showing C causes the customer to spend more money than they otherwise would have.

Eventually the system can score:

Score(C)=P(C acceptedbasket,customer,context)×Margin(C)\text{Score}(C) = P(C \text{ accepted} \mid \text{basket}, \text{customer}, \text{context}) \times \text{Margin}(C)

Then consider a cart of Protein €35 + Creatine €25 = €60:

CandidateAcceptanceMarginExpected margin
Shaker €1024%€6€1.44
Protein Bar €335%€1€0.35
Pre-Workout €3011%€18€1.98
BCAA €208%€12€0.96

The shaker has the strongest basket relationship. The protein bar has the highest conversion probability. Pre-workout creates the highest expected incremental margin - so it might be the optimal recommendation.

That is no longer standard “people also bought.” It is an optimization engine.

Another advantage: you see the actual cart

This is especially relevant for an upsell product. Shopify’s documented recommendation query takes a product as its input.

The engine can take customer, entire cart, order history, cart value, current products, product prices, inventory, previous purchases, upsell impression history, and upsell conversion history - and answer: what should I show this particular customer right now?

Instead ofRecommendation(Product)\text{Recommendation}(\text{Product}), the target isRecommendation(Customer,Basket,Context)\text{Recommendation}(\text{Customer}, \text{Basket}, \text{Context}):

argmaxxE[IncrementalMarginCustomer,Basket,Context,Show(x)]\operatorname*{argmax}_{x} \mathbb{E}[\text{IncrementalMargin} \mid \text{Customer}, \text{Basket}, \text{Context}, \text{Show}(x)]

That is considerably more ambitious.

How to position the idea

Do not pitch it as “we have a better recommendation algorithm than Shopify.” That is hard to substantiate because Shopify does not disclose everything internally.

A better frame: Shopify recommends products. We optimize the next purchase.

Or technically: traditional recommendations ask which products are related. A basket completion engine asks which product is missing from this specific basket - and eventually which recommendation creates the highest expected incremental value.

That distinction is both defensible and, for an upsell product, much more interesting.

Share article