Skip to content
Simple Cart Upsell Toggle (version 2) - Free Tutorial
Browse other ways to boost conversion rate & profit

Simple Cart Upsell Toggle (version 2) - Free Tutorial

Want personalized guidance adding this to your store?

Check out our Insiders community: https://www.skool.com/the-prompted

Members of The Prompted community receive a detailed store audit, 1-on-1 guidance for implementing new features, and access to an exclusive theme. You'll also get marketing support, the same tactics we use to spend over $100k/mo on Meta Ads.

---

In this tutorial, we’re looking at adding a simple upsell to your shopify cart, like adding the option for shipping protection or gift wrapping.

This is actually an updated version of our previous simple cart customization, but you were asking how we can make the upsell enabled by default, so we’ve added that functionality in.

Compatible Themes: This code should work on all free Shopify themes (Dawn, Refresh, Craft, Studio, Publisher, Crave, Origin, Taste, Colorblock, Sense, Ride, Spotlight).

 

Add settings to the theme editor

Edit settings_schema.json

  {
    "name": "Cart Simple Upsell",
    "settings": [
      {
        "type": "checkbox",
        "id": "enable_cart_upsell",
        "label": "Use Cart Upsell",
        "default": false
      },
      {
        "type": "checkbox",
        "id": "default_upsell_enabled_in_cart",
        "label": "Upsell is in cart by default",
        "default": true
      },
      {
        "type": "text",
        "id": "cart_upsell_variant_id",
        "label": "Product variant ID",
        "placeholder": "Enter the upsell product variant ID",
        "info": "Note: Products with only the default variant still have a variant ID that differs from product ID."
      },
      {
        "type": "text",
        "id": "cart_upsell_toggle_text",
        "label": "Upsell Toggle Text",
        "default": "Add Upsell Product"
      },
      {
        "type": "select",
        "id": "cart_upsell_alignment",
        "label": "Upsell Toggle Alignment",
        "options": [
          {
            "value": "left",
            "label": "Left"
          },
          {
            "value": "right",
            "label": "Right"
          }
        ],
        "default": "left"
      },
      {
        "type": "color",
        "id": "cart_upsell_toggle_color",
        "label": "Upsell Toggle Color",
        "default": "#4CAF50"
      }
    ]
  }

Modify add to cart for option to add upsell product by default

Edit buy-buttons.liquid

Add data attribute to the product-form

{% if settings.cart_upsell_variant_id and settings.default_upsell_enabled_in_cart %}data-upsell-variant-id="{{ settings.cart_upsell_variant_id }}"{% endif %}

Edit product-form.js

Add to the ProductForm constructor

this.upsellVariantId = this.dataset.upsellVariantId;

Modify the onSubmitHandler fetch

        fetch(`${routes.cart_url}.js`)
          .then((response) => response.json())
          .then((cartState) => {
            if (cartState.item_count === 0 && this.upsellVariantId) {
              return fetch(`${routes.cart_add_url}`, this.getUpsellProductConfig());
            }
          })
          .then(() => {
            return fetch(`${routes.cart_add_url}`, config);
          })

Add a new method getUpsellProductConfig

      getUpsellProductConfig() {
        const upsellFormData = new FormData();
        upsellFormData.append('id', this.upsellVariantId);
        upsellFormData.append('quantity', 1);

        const config = fetchConfig('javascript');
        config.headers['X-Requested-With'] = 'XMLHttpRequest';
        delete config.headers['Content-Type'];
        config.body = upsellFormData;

        return config;
      }

Add upsell toggle to the cart

Edit main-cart-items.liquid

Replace the cart.js script tag with the following

  {% if settings.enable_cart_upsell %}
    <script src="{{ 'cart.js' | asset_url }}" defer="defer" data-cart-upsell-variant-id="{{ settings.cart_upsell_variant_id }}"></script>
  {% else %}
    <script src="{{ 'cart.js' | asset_url }}" defer="defer"></script>
  {% endif %}

Edit main-cart-footer.liquid

Add render for custom liquid file

{% render 'cart-upsell-custom' %}

Edit cart-drawer.liquid

Replace the cart.js script tag with the following

{% if settings.enable_cart_upsell %}
  <script src="{{ 'cart.js' | asset_url }}" defer="defer" data-cart-upsell-variant-id="{{ settings.cart_upsell_variant_id }}"></script>
{% else %}
  <script src="{{ 'cart.js' | asset_url }}" defer="defer"></script>
{% endif %}

Add render for custom liquid file

{% render 'cart-upsell-custom' %}

Add new snippet cart-upsell-custom.liquid

{% if settings.enable_cart_upsell %}
  <div class="cart-upsell-toggle-container cart-upsell-toggle-container--{{ settings.cart_upsell_alignment }} {% if cart.item_count == 0 %}hidden{% endif %}">
    <label class="cart-upsell-toggle-label">
      <input type="checkbox" id="cart-upsell-toggle" class="cart-upsell-toggle" {% if upsell_in_cart %}checked{% endif %}>
      <span class="cart-upsell-toggle-slider"></span>
      {{ settings.cart_upsell_toggle_text }}
    </label>
  </div>
  
<style>        

  .cart-upsell-toggle-container {
    display: flex;
    justify-content: {{ settings.cart_upsell_alignment }};

    {% if settings.cart_type == 'drawer' %}
        border-bottom: .1rem solid rgba(var(--color-foreground), .2);
        padding-bottom: 1rem;
    {% endif %}
  }

  .cart-upsell-toggle-container.hidden {
    display: none;
  }
    
  .cart-upsell-toggle-label {
    display: inline-flex;
    align-items: center;
    cursor: pointer;
  }
  
  .cart-upsell-toggle {
    position: absolute;
    opacity: 0;
    height: 0;
    width: 0;
  }
  
  .cart-upsell-toggle-slider {
    position: relative;
    display: inline-block;
    width: 40px;
    height: 20px;
    background-color: #ccc;
    border-radius: 20px;
    margin-right: 10px;
    transition: background-color 0.3s ease;
  }
  
  .cart-upsell-toggle-slider::before {
    content: "";
    position: absolute;
    height: 16px;
    width: 16px;
    left: 2px;
    bottom: 2px;
    background-color: white;
    border-radius: 50%;
    transition: transform 0.3s ease;
  }
  
  .cart-upsell-toggle:checked + .cart-upsell-toggle-slider {
    background-color: {{ settings.cart_upsell_toggle_color }};
  }
  
  .cart-upsell-toggle:checked + .cart-upsell-toggle-slider::before {
    transform: translateX(20px);
  }

  #Details-CartDrawer {
    margin-top: 0;
  }
    
</style>
    
{% endif %}

Edit cart.js

Add to CartItems constructor

    const cartUpsellToggle = document.getElementById('cart-upsell-toggle');
    if (cartUpsellToggle) {
      cartUpsellToggle.addEventListener('change', this.onCartUpsellToggle.bind(this));
    }

Add to connectedCallback method

    if (this.tagName !== 'CART-DRAWER-ITEMS') {
      fetch(`${routes.cart_url}.js`)
        .then((response) => response.json())
        .then((parsedState) => {
          this.updateCartUpsellToggleState();
          this.updateCartUpsellVisibility(parsedState.item_count);
        })
        .catch((e) => {
          console.error(e);
        });
    }

Add to onCartUpdate method (cart drawer)

          const parsedStateElement = html.querySelector('[data-cart-drawer-state]');
          const parsedState = parsedStateElement ? JSON.parse(parsedStateElement.textContent) : null;
          this.updateCartUpsellToggleState();
          if (parsedState) {
            this.updateCartUpsellVisibility(parsedState.item_count);
          }

Add to onCartUpdate method (cart page)

          const parsedStateElement = html.querySelector('[data-cart-state]');
          const parsedState = parsedStateElement ? JSON.parse(parsedStateElement.textContent) : null;
          this.updateCartUpsellToggleState();
          if (parsedState) {
            this.updateCartUpsellVisibility(parsedState.item_count);
          }

Add new upsell toggle methods

  updateCartUpsellToggleState() {
    const cartUpsellToggle = document.getElementById('cart-upsell-toggle');
    const scriptTag = document.querySelector('script[data-cart-upsell-variant-id]');
    const cartUpsellVariantId = scriptTag ? scriptTag.dataset.cartUpsellVariantId : '';
    const cartItems = document.querySelectorAll('.cart-item');

    const upsellItem = Array.from(cartItems).find(item => {
      const input = item.querySelector('input[data-quantity-variant-id]');
      return input && input.getAttribute('data-quantity-variant-id') === cartUpsellVariantId;
    });

    if (cartUpsellToggle && cartUpsellToggle.checked !== !!upsellItem) {
      cartUpsellToggle.checked = !!upsellItem;
    }
  }

  updateCartUpsellVisibility(itemCount) {
    const cartUpsellContainer = document.querySelector('.cart-upsell-toggle-container');
    if (cartUpsellContainer) {
      if (itemCount === 0) {
        cartUpsellContainer.classList.add('hidden');
      } else {
        cartUpsellContainer.classList.remove('hidden');
      }
    }
  }

  onCartUpsellToggle(event) {
    const scriptTag = document.querySelector('script[data-cart-upsell-variant-id]');
    const cartUpsellVariantId = scriptTag ? scriptTag.dataset.cartUpsellVariantId : '';
    const isChecked = event.target.checked;

    if (isChecked) {
      this.addUpsellProduct(cartUpsellVariantId);
    } else {
      if (!this.removingUpsellProduct) {
        this.removingUpsellProduct = true;
        this.removeUpsellProduct(cartUpsellVariantId);

        if (this.tagName === 'CART-DRAWER-ITEMS') {
          event.stopImmediatePropagation();
        }
      }
    }
  }

  async addUpsellProduct(cartUpsellVariantId) {
    const upsellFormData = new FormData();
    upsellFormData.append('id', cartUpsellVariantId);
    upsellFormData.append('quantity', 1);

    const config = fetchConfig('javascript');
    config.headers['X-Requested-With'] = 'XMLHttpRequest';
    delete config.headers['Content-Type'];
    config.body = upsellFormData;

    const response = await fetch(`${routes.cart_add_url}`, config);
    if (!response.ok) {
      const errorText = await response.text();
      console.error('Failed to add upsell product:', errorText);
      throw new Error('Failed to add upsell product');
    }

    this.onCartUpdate();
  }

  async removeUpsellProduct(cartUpsellVariantId) {
    const cartItems = document.querySelectorAll('.cart-item');

    const upsellItem = Array.from(cartItems).find(item => {
      const input = item.querySelector('input[data-quantity-variant-id]');
      return input && input.getAttribute('data-quantity-variant-id') === cartUpsellVariantId;
    });

    if (!upsellItem) {
      console.error('Upsell product not found in the cart.');
      return;
    }

    const upsellIndex = upsellItem.querySelector('input[data-index]').dataset.index;

    try {
      await this.updateQuantity(upsellIndex, 0, null, cartUpsellVariantId);
      this.removingUpsellProduct = false;
    } catch (error) {
      console.error('Error removing upsell product:', error);
      this.removingUpsellProduct = false;
    }
  }

Add to updateQuantity method

        this.updateCartUpsellToggleState();
        this.updateCartUpsellVisibility(parsedState.item_count);

Edit cart-drawer.js

Add to the open method

    const cartDrawerItems = this.querySelector('cart-drawer-items');
    if (cartDrawerItems) {
      cartDrawerItems.updateCartUpsellToggleState();
      cartDrawerItems.updateCartUpsellVisibility();
    }

Browse other ways to boost conversion rate & profit