Skip to main content
Lead Generation Websites, Google Maps Ranking, WhatsApp Funnels, Ecommerce, SEO, Web DesignSpeed Optimization · Conversion Optimization · Monthly Lead Systems · AI AutomationLead Generation Websites, Google Maps Ranking, WhatsApp Funnels, Ecommerce, SEO, Web Design

Cart Drawer Upsells Without Apps: Build High-Converting Bundles in Shopify Theme Code

Published: January 25, 2026
Written by Sumeet Shroff
01.25.26
Cart Drawer Upsells Without Apps: Build High-Converting Bundles in Shopify Theme Code
Table of Contents
  1. Introduction
  2. Why build upsells in the theme (not an app)
  3. What we’ll build
  4. File changes overview
  5. Implementation: Liquid + JavaScript
  6. Product recommendation strategies
  7. Threshold logic patterns
  8. UX patterns and accessibility
  9. Comparison: App-based vs Theme-based cart drawer upsell
  10. Tracking & analytics
  11. A/B test and validation plan
  12. Real-World Scenarios
  13. Scenario 1: The Niche Cosmetics Store
  14. Scenario 2: The Outdoor Gear Retailer
  15. Scenario 3: The Subscription Starter Kit
  16. Checklist
  17. Checklist
  18. Latest News & Trends
  19. Comparison table: Tracking approaches
  20. Key implementation tips
  21. Conclusion
  22. External resources
  23. About Prateeksha Web Design
In this guide you’ll learn
  • How to implement a shopify cart drawer upsell without apps using Liquid + JS
  • Bundle and threshold logic, UX patterns, and product recommendation techniques
  • How to track, test, and validate conversion impact

Introduction

Adding targeted upsells in the cart drawer is one of the highest-ROI conversion moves for many Shopify merchants. This tutorial shows how to implement a shopify cart drawer upsell without apps by editing theme Liquid templates and adding a small JavaScript module. You’ll get code examples, UX patterns, tracking, and a simple experiment plan.

Fact Directly implementing upsell logic in theme code reduces third-party script bloat and can improve performance and control.

Why build upsells in the theme (not an app)

  • Lower monthly costs and fewer external dependencies.
  • Full control over UI and checkout flow consistency.
  • Easier to integrate with theme variables, metafields, and custom logic.

However, remember security and upgrade complexity: theme code changes require care and versioning.

What we’ll build

  • A cart drawer that shows dynamic product recommendations.
  • Threshold-based messaging (e.g., "Add $X for free shipping" or "Add 1 more to get a bundle discount").
  • Buttons to add recommended items into the drawer without a full page reload.
  • Analytics events for tracking impressions, clicks, and conversions.

File changes overview

  • Sections/cart-drawer.liquid (or snippet)
  • Assets/cart-drawer.js
  • Assets/cart-drawer.css (optional)
  • theme.liquid or layout to include scripts

Implementation: Liquid + JavaScript

Below is a simplified pattern. Adjust to your theme structure (Dawn, Sense, etc.).

  1. cart-drawer.liquid (snippet or section)
<div id="cart-drawer" class="cart-drawer" aria-hidden="true">
  <div class="cart-drawer__header">
    <h2>Your cart</h2>
    <button class="cart-drawer__close" aria-label="Close">×</button>
  </div>

<div id="cart-items" class="cart-items">{% include 'cart-items' %}</div>

<div id="cart-upsell" class="cart-upsell" data-upsell-variants='{{ shop.metafields.custom.upsell_variants | json }}'> <!-- Fallback content: JavaScript will render recommendations here --> <div class="cart-upsell__loading">Loading recommendations…</div> </div>

<div class="cart-drawer__footer"> <div class="cart-total">Total: <span id="cart-total">{{ cart.total_price | money }}</span></div> <a href="/cart" class="btn btn--primary">View Cart</a> </div> </div>

Notes:

  • We store recommended variant IDs in a metafield (shop.metafields.custom.upsell_variants) or use in-theme logic to render related product data server-side when possible.
  • The JS will fetch product details via the Storefront or AJAX endpoints when needed.
  1. cart-drawer.js (client-side behaviors)
// assets/cart-drawer.js
(function(){
  const drawer = document.getElementById('cart-drawer');
  const upsellContainer = document.getElementById('cart-upsell');
  const closeBtn = drawer.querySelector('.cart-drawer__close');

function openDrawer(){ drawer.setAttribute('aria-hidden', 'false'); } function closeDrawer(){ drawer.setAttribute('aria-hidden', 'true'); }

closeBtn.addEventListener('click', closeDrawer);

// Read upsell candidates from data attribute const upsellData = JSON.parse(upsellContainer.dataset.upsellVariants || '[]');

async function fetchProduct(variantId){ // Use AJAX cart or product endpoints to get minimal details // Example: fetch /products.json isn't available; instead fetch product by handle or use pre-rendered JSON in Liquid // For demo, assume theme stored a product-list JSON under window.UPSELL_PRODUCTS return window.UPSELL_PRODUCTS ? window.UPSELL_PRODUCTS[variantId] : null; }

function renderUpsells(items, cartTotal){ if(!items || items.length===0){ upsellContainer.innerHTML = ''; return; }

const listHtml = items.map(item=&gt;{
  const meetsThreshold = item.price + cartTotal &gt;= item.threshold_amount; // example
  return `
    &lt;div class=&quot;upsell-card&quot; data-variant=&quot;${item.id}&quot;&gt;
      &lt;img src=&quot;${item.image}&quot; alt=&quot;${item.title}&quot; /&gt;
      &lt;div class=&quot;upsell-card__meta&quot;&gt;
        &lt;div class=&quot;title&quot;&gt;${item.title}&lt;/div&gt;
        &lt;div class=&quot;price&quot;&gt;${item.price_display}&lt;/div&gt;
        &lt;button class=&quot;btn btn--small add-upsell&quot;&gt;Add&lt;/button&gt;
        ${ meetsThreshold ? '&lt;div class=&quot;badge&quot;&gt;Unlocks discount&lt;/div&gt;' : '' }
      &lt;/div&gt;
    &lt;/div&gt;`;
}).join('');

upsellContainer.innerHTML = `&lt;div class=&quot;upsell-list&quot;&gt;${listHtml}&lt;/div&gt;`;
attachAddHandlers();

// Track impression
window.dataLayer &amp;&amp; window.dataLayer.push({event: 'upsell_impression', upsell_count: items.length});

}

function attachAddHandlers(){ upsellContainer.querySelectorAll('.add-upsell').forEach(btn=>{ btn.addEventListener('click', async function(e){ const variantId = this.closest('.upsell-card').dataset.variant; // Add to cart via AJAX await addItemToCart(variantId, 1); // Track click window.dataLayer && window.dataLayer.push({event: 'upsell_click', variant_id: variantId}); // Optionally refresh cart items refreshCartItems(); }); }); }

async function addItemToCart(variantId, qty){ return fetch('/cart/add.js', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({id: variantId, quantity: qty}) }).then(r=>r.json()); }

async function refreshCartItems(){ const res = await fetch('/cart?view=drawer'); // custom cart template that returns HTML const html = await res.text(); document.getElementById('cart-items').innerHTML = html; // Recalculate cart total for threshold logic const cartTotal = parseFloat(document.getElementById('cart-total').dataset.cents) || 0; // Render upsells again with updated cartTotal // In practice, fetch recommended products with prices and threshold logic server-side or via stored JSON const upsellProducts = (window.UPSELL_PRODUCTS_ARRAY || []).map(p=>({ id: p.id, title: p.title, image: p.image, price: p.price, price_display: p.price_display, threshold_amount: p.threshold_amount })); renderUpsells(upsellProducts, cartTotal); }

// Initial render document.addEventListener('DOMContentLoaded', function(){ // Example: window.UPSELL_PRODUCTS_ARRAY is prepopulated via Liquid const cartTotal = window.CART_TOTAL || 0; renderUpsells(window.UPSELL_PRODUCTS_ARRAY || [], cartTotal); }); })();

Tip Pre-render as much data as possible with Liquid (images, titles, formatted prices). Only fetch dynamic bits (real-time inventory) on demand to reduce network calls.

Product recommendation strategies

  • Metafield lists: store a curated list of variant IDs in a shop or product metafield and surface them in the drawer.
  • Related-by-collection: show top sellers from the same collection as the last added item.
  • Frequently-bought-together: compute pairs server-side or via exports and store in theme data.
  • Threshold-driven offers: e.g., "Add $15 for free shipping" or "Add one more for 20% off bundle".

Combining curated lists with dynamic thresholds often outperforms pure algorithmic recommendations for small catalogs.

Threshold logic patterns

  • Free Shipping Threshold: calculate remaining amount to reach free shipping; show single low-cost item suggestions that close the gap.
  • Bundle Discount Threshold: when cart contains X, recommend complementary Y with discount when both are in cart.
  • Volume Upsell: "Add 2 for $X each" messaging for consumables.

Implementation tips:

  • Keep calculations in cents to avoid floating errors.
  • Prefer server-side calculation for checkout-affecting discounts (Shopify Scripts for Plus or Discount Codes for standard).

UX patterns and accessibility

  • Make the add action reversible and obvious — show a mini toast "Added to cart" and allow removal.
  • Keyboard focus management: when the cart drawer opens, trap focus inside and return focus when closed.
  • Screen reader announcements: announce upsell addition and updated totals.
  • Avoid surprising upsells: clearly show price and inventory.

Include aria attributes and follow W3C Web Accessibility Initiative guidelines for drawer components.

Comparison: App-based vs Theme-based cart drawer upsell

Here’s a short comparison to help you decide which approach to use.

FeatureApp-based upsellTheme-based (no app)
Monthly costApp fee (ongoing)No extra cost (one-time dev)
Performance impactPotential third-party scriptControlled by theme (smaller)
Custom UI flexibilityLimited by appFull control via Liquid + CSS
MaintenanceApp updates managed externallyRequires developer to update

Tracking & analytics

Key events to track (push to dataLayer or your analytics):

  • upsell_impression — when recommended items rendered
  • upsell_click — user clicks "Add" on a recommended product
  • upsell_add_confirm — item successfully added
  • upsell_conversion — recommended SKU purchased in the same session

Example dataLayer push on add (already shown in JS). Connect these to Google Analytics / GA4 or measurement platform. Follow Google Search Central and Google Lighthouse best practices to minimize performance penalties.

A/B test and validation plan

  • Hypothesis: Adding curated suggestions in the cart drawer increases AOV by X% without hurting conversion rate.
  • Metrics: AOV, conversion rate, add-to-cart rate for recommendations, bounce rate.
  • Sample: Run the experiment for a full business cycle (at least 2 weeks) and use statistical significance tools.

Use server-side flags or client-side experiment tools to toggle the drawer upsell for treatment vs control.

Real-World Scenarios

Scenario 1: The Niche Cosmetics Store

A small cosmetics brand added a single curated face-serum to the cart drawer as a recommended add-on. By keeping imagery and copy concise, they increased average order value and avoided app fees. They pre-rendered product data via metafields and saw minimal performance impact.

Scenario 2: The Outdoor Gear Retailer

An outdoor retailer implemented a "add $25 for free shipping" threshold in the cart drawer. They suggested small but relevant accessories (carabiner, socks). The threshold logic was calculated in theme code and tracked via dataLayer to measure conversion lift.

Scenario 3: The Subscription Starter Kit

A merchant selling consumables suggested a 2-pack discount in the drawer (volume upsell). They used Liquid to show discounted price messaging and JS to update the cart. Results showed improved repeat purchase likelihood for customers who accepted the bundle.

Checklist

Checklist

  • Back up current theme and work in a development theme
  • Decide how recommendations are sourced (metafields, collection, analytics)
  • Pre-render product metadata in Liquid when possible
  • Implement accessible drawer (focus trap, aria attributes)
  • Add analytics events: impression, click, add, conversion
  • Test add-to-cart flows, edge cases (sold out, inventory changes)
  • Run A/B test and collect at least two weeks of data

Latest News & Trends

  • Headless and hybrid architectures are making theme-level experiments lighter and more performant.
  • Privacy-first tracking changes (cookieless) increase the importance of server-side attribution and first-party events.
  • Performance budgets now factor into UX decisions: prefer server-rendered micro-data and minimal client JS.
Warning Avoid hardcoding discount logic that impacts checkout totals without testing; use Shopify Discount Codes or Scripts (Plus) for authoritative pricing adjustments.

Comparison table: Tracking approaches

Below is a short table comparing tracking approaches for upsell events.

ApproachComplexityReliability
Client-side dataLayer pushLowMedium (adblockers)
Server-side events (webhooks)HighHigh
Hybrid (client + server)MediumHigh

Key implementation tips

  • Precompute recommended product JSON in Liquid and expose as a JS variable to avoid product endpoint latency.
  • Keep UI copy concise; show clear price and benefits.
  • Use thresholds that match shipping/discount rules to avoid mismatched expectations.
Key takeaways
  • Implementing cart drawer upsells in theme code reduces dependencies and improves control.
  • Use pre-rendered Liquid data for images and prices; use JS for interactions.
  • Track impressions, clicks, adds, and conversions to measure impact.
  • Test thresholds and UX patterns with an A/B experiment before sitewide rollout.
  • Follow accessibility best practices and performance budgets.

Conclusion

Implementing a shopify cart drawer upsell without apps is entirely feasible and often preferable for merchants who want fast, tailored UI and lower recurring costs. By combining Liquid pre-rendered data, a small, focused JavaScript module, clear threshold rules, and analytics eventing, teams can deliver high-converting bundles with controlled performance.

External resources

Tip If you have a large catalog, combine curated lists with algorithmic suggestions; for small catalogs, curated lists often convert better.

About Prateeksha Web Design

Prateeksha Web Design builds conversion-focused Shopify themes and experiments, specializing in theme-level upsells, custom Liquid code, and performance-conscious UX for ecommerce brands.

Chat with us now Contact us today.

Sumeet Shroff
Sumeet Shroff
Sumeet Shroff is a renowned expert in web design and development, sharing insights on modern web technologies, design trends, and digital marketing.

Comments

Leave a Comment

Loading comments...