Delete Shopify cart items using AJAX without refreshing the page

Need help with AJAX cart item deletion in Shopify

I’m working on a shopping cart feature where users can delete items without reloading the page. Right now I have a basic link that works but it refreshes the whole page which is not what I want.

The current approach uses a simple link like this but it causes page reload. I need a solution that works with AJAX to make the cart update smoothly.

My shopping cart modal structure

<div class="shopping-modal" id="shoppingModal">
    <form action="/cart" method="post" novalidate>
        <div class="modal-header d-flex flex-row align-items-center justify-content-between">
            <h3>Shopping Bag</h3>
            <button class="close-modal" role="button" type="button">
                <i class="fa fa-close"></i>
            </button>
        </div>
        <div class="modal-content d-flex flex-column">
            {% for product in cart.items %}
                <div class="cart-item d-flex flex-row">
                    <div class="item-photo">
                        <a href="{{ product.url | within: collections.all }}">
                            <img class="img-responsive" src="{{ product | img_url: 'large' }}" alt="{{ product.title }}">
                        </a>
                    </div>
                    <div class="item-details d-flex flex-column">
                        <h4><a href="{{ product.url }}">{{ product.product.title }}</a></h4>
                        <span class="option">{{ product.variant.title }}</span>
                        <span class="cost">${{ product.price | money }}</span>
                        <div class="amount-selector d-flex flex-row">
                            <button class="qty-btn decrease" data-id="{{ product.variant.id }}">-</button>
                            <input class="qty-field" type="number" value="{{ product.quantity }}" min="1" />
                            <button class="qty-btn increase" data-id="{{ product.variant.id }}">+</button>
                        </div>
                        <div class="actions">
                            <a class="delete-item" data-id="{{ product.variant.id }}">Delete</a>
                        </div>
                    </div>
                </div>
            {% endfor %}
        </div>
        <div class="modal-footer">
            <div class="total-amount d-flex justify-content-between">
                <span>Grand Total</span>
                <span class="amount">${{ cart.total_price | money }}</span>
            </div>
            <div class="action-buttons d-flex">
                <button class="checkout-btn" type="submit">Proceed</button>
                <a class="view-cart-btn" href="{{ routes.cart_url }}">View All</a>
            </div>
        </div>
    </form>
</div>

JavaScript for adding products

$('.product-option').on('click', function(){
    var element = $(this);
    var productId = $(this).attr("data-id");
    $.ajax({
        type: 'POST',
        url: '/cart/add.js',
        data: {
            quantity: 1,
            id: productId
        },
        dataType: 'json',
        success: function (response) {
            updateCartDisplay();
        }
    });
});

function updateCartDisplay() {
    $.ajax({
        type: 'GET',
        dataType: 'jsonp',
        url: '/cart.json',
        success: function(cartData){
            var itemCount = cartData['item_count'];
            var totalCost = cartData['total_price']/100;
            
            if (itemCount > 0) {
                $('.cart-counter span').text(itemCount);
                $('.modal-footer .amount').text('$' + totalCost.toFixed(2));
                
                var itemsList = [];
                for(var i = 0; i < itemCount; i++){
                    var item = cartData['items'][i];
                    var itemHtml = buildCartItemHtml(item);
                    itemsList.push(itemHtml);
                }
                $('.modal-content').html(itemsList.join(''));
            }
        }
    });
}

Decrease quantity handler

$('.shopping-modal').on('click', '.qty-btn.decrease', function(){
    var currentQty = parseInt($(this).next().val());
    if (currentQty > 1) {
        $(this).next().val(currentQty - 1);
    }
    var productId = $(this).attr("data-id");
    var newQty = $(this).next().val();
    
    updateProductQuantity(productId, newQty);
});

Increase quantity handler

$('.shopping-modal').on('click', '.qty-btn.increase', function(){
    var currentQty = parseInt($(this).prev().val());
    $(this).prev().val(currentQty + 1);
    var productId = $(this).attr("data-id");
    
    $.ajax({
        type: 'POST',
        url: '/cart/add.js',
        data: {
            quantity: 1,
            id: productId
        },
        success: function(){
            updateCartTotals();
        }
    });
});

My attempt at item removal

$('.shopping-modal').on('click', '.delete-item', function(e){
    e.preventDefault();
    var productId = $(this).attr('data-id');
    
    $.ajax({
        type: 'POST',
        url: '/cart/update.js',
        data: {
            quantity: 0,
            id: productId
        },
        success: function(){
            updateCartDisplay();
        }
    });
});

Two main questions I have:

  1. Why is my AJAX delete function not working properly to remove items from the cart modal?
  2. Will this same approach work on the main cart page or do I need different code for that?

use /cart/update.js with updates[variantId] as the object key. you’re missing dataType in your ajax call - that’ll cause problems. this worked for me: $.ajax({ type: 'POST', url: '/cart/update.js', dataType: 'json', data: 'updates[' + variantId + ']=0', success: function(){ location.reload(); } }); quick and dirty but gets the job done.

Your AJAX setup looks mostly right, but there’s a critical issue with how you’re handling the cart update response. The /cart/update.js endpoint returns the updated cart object directly - you should use that data instead of making another call to updateCartDisplay(). Here’s my working solution:

$('.shopping-modal').on('click', '.delete-item', function(e){
    e.preventDefault();
    var variantId = $(this).attr('data-id');
    var cartItem = $(this).closest('.cart-item');
    
    $.ajax({
        type: 'POST',
        url: '/cart/change.js',
        data: {
            quantity: 0,
            id: variantId
        },
        dataType: 'json',
        success: function(cart){
            cartItem.fadeOut(300, function() {
                $(this).remove();
            });
            $('.cart-counter span').text(cart.item_count);
            $('.modal-footer .amount').text('$' + (cart.total_price/100).toFixed(2));
        }
    });
});

I switched from /cart/update.js to /cart/change.js since it works better for single item updates. The key difference is using the returned cart data immediately rather than triggering another AJAX call. This works the same way on your main cart page.

You’re overthinking this. Stop wrestling with Shopify’s cart APIs and automate the whole thing.

I had the same problem with a client’s store - laggy cart updates, frustrated users. Instead of debugging endless AJAX calls, I used Latenode automation. Way smoother.

Your setup’s making multiple API calls - one to update, another to refresh. That’s slow and creates race conditions.

Latenode workflow:

  • Catches delete action
  • Updates cart via Shopify API
  • Updates frontend instantly without multiple round trips
  • Handles errors properly
  • Works on both cart drawer and main page

It’s faster than your AJAX setup because everything processes server-side first, then sends one clean response to update your UI.

I’ve done this for three Shopify stores. Cart operations are noticeably snappier - no loading states or flickering for users.

You can extend it for quantity updates, discounts, or abandoned cart emails too.

Check it out: https://latenode.com

Had this exact problem last month building a custom cart drawer. Your delete function’s mixing up the data format for /cart/update.js. Don’t send id and quantity - you need to send updates as an object where the variant ID is the key. Here’s what fixed it for me: $(‘.shopping-modal’).on(‘click’, ‘.delete-item’, function(e){ e.preventDefault(); var variantId = $(this).attr(‘data-id’); var updates = {}; updates[variantId] = 0; $.ajax({ type: ‘POST’, url: ‘/cart/update.js’, data: { updates: updates }, dataType: ‘json’, success: function(cart){ updateCartDisplay(); }, error: function(xhr, status, error) { console.log(‘Error removing item:’, error); } }); }); For your second question about the main cart page - yeah, this same approach works great there too. I’m using identical code on both my cart drawer and cart page templates. Just make sure your HTML structure has the same data attributes and class names.

Your problem is mixing endpoints - the quantity handlers use add.js while delete tries update.js with wrong parameters. I hit this same issue building cart functionality last year. Just use change.js for everything, including deletions. Here’s the fix: javascript $('.shopping-modal').on('click', '.delete-item', function(e){ e.preventDefault(); var variantId = $(this).attr('data-id'); $.ajax({ type: 'POST', url: '/cart/change.js', data: { quantity: 0, id: variantId }, dataType: 'json', success: function(cart){ updateCartDisplay(); } }); }); Change.js handles both updates and removals way better than mixing endpoints. Works the same on your main cart page too since Shopify’s API stays consistent across all cart contexts.

The problem is how you’re sending data to /cart/update.js. That endpoint wants an ‘updates’ parameter with variant IDs as keys, not separate id and quantity fields. I’ve worked with Shopify APIs for three years and this got me too at first. You’re sending {quantity: 0, id: productId} but it needs {updates: {productId: 0}}. Try this: var updates = {}; updates[productId] = 0; $.ajax({type: ‘POST’, url: ‘/cart/update.js’, data: {updates: updates}, dataType: ‘json’, success: function(response){updateCartDisplay();}}); Add error handling since network issues can mess up your UI state. For your second question - yes, this works on the main cart page too. The cart API works the same whether you’re on a product page, cart page, or using a modal.