Remove a Product From the WooCommerce Mini Cart Using AJAX

Quick Summary
- A remove link built with woocommerce_cart_item_remove_link, a jQuery click handler and a PHP Ajax function remove mini cart items without reloading.
- The link keeps WooCommerce's normal remove URL as a no-JavaScript fallback, and the script posts both IDs with the product_remove action.
- WooCommerce's add-to-cart script must be loaded, selectors must match your markup, and the handler doesn't check a nonce, so consider adding one.
These snippets let shoppers remove a product from a custom WooCommerce mini cart without reloading the page. You need three parts: a remove link for each cart item, a jQuery click handler that sends an Ajax request, and a PHP function that removes the item and returns updated cart HTML.
The remove link for each mini cart item
Place this inside the loop that prints your mini cart items, such as the one in building a header mini cart for a custom theme. It needs three variables from that loop: $cart_item_key, $product_id and the product object $_product. If you loop over WC()->cart->get_cart() as $cart_item_key => $cart_item, the product ID is $cart_item['product_id'] and the product object is $cart_item['data'].
<?php echo apply_filters( 'woocommerce_cart_item_remove_link', sprintf(
'<a href="%s" class="remove" aria-label="%s" data-product_id="%s" data-product_sku="%s" data-cart_item_key="%s">×</a>',
esc_url( WC()->cart->get_remove_url( $cart_item_key ) ),
esc_html__( 'Remove this item', 'nmh47' ),
esc_attr( $product_id ),
esc_attr( $_product->get_sku() ),
esc_attr( $cart_item_key )
), $cart_item_key ); ?>The link points to WooCommerce’s normal remove URL, so it still works without JavaScript. The data- attributes pass the product ID and cart item key to the script. Running it through the woocommerce_cart_item_remove_link filter lets other plugins adjust it, as they can in WooCommerce’s own cart templates. Replace the nmh47 text domain with your theme’s.
The click handler
Add this to your theme’s main JS file. It stops the link from loading, shows a loader and posts the IDs to WooCommerce’s Ajax URL with the action product_remove. The returned fragments then replace the matching elements on the page, and the item is removed from the list. The code runs inside jQuery(function ($) { ... }) because WordPress loads jQuery in no-conflict mode, where $ on its own is undefined.
// Ajax delete product from the cart
jQuery(function ($) {
$(document).on('click', '.header-cart-cart-item a.remove', function (e)
{
e.preventDefault();
var product_id = $(this).attr("data-product_id"),
cart_item_key = $(this).attr("data-cart_item_key"),
product_container = $(this).closest('.header-cart-cart-item');
// Add loader
product_container.block({
message: null,
overlayCSS: {
cursor: 'none'
}
});
$.ajax({
type: 'POST',
dataType: 'json',
url: wc_add_to_cart_params.ajax_url,
data: {
action: "product_remove",
product_id: product_id,
cart_item_key: cart_item_key
},
success: function(response) {
if ( ! response || response.error )
return;
var fragments = response.fragments;
// Replace fragments
if ( fragments ) {
$.each( fragments, function( key, value ) {
$( key ).replaceWith( value );
});
}
// Remove the item from the header list
product_container.remove();
}
});
});
});The click selector expects the link inside .header-cart-cart-item, and the loader covers that same item. Change the class if your markup uses another one. wc_add_to_cart_params and .block() come from WooCommerce’s add-to-cart script, which must be loaded on the page.
The Ajax handler to remove the item from the WooCommerce mini cart
Add this to functions.php. The wp_ajax_product_remove and wp_ajax_nopriv_product_remove hooks connect it to the product_remove action for logged-in users and guests.
// Remove product in the cart using ajax
function warp_ajax_product_remove()
{
// Get mini cart
ob_start();
foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item)
{
if($cart_item['product_id'] == $_POST['product_id'] && $cart_item_key == $_POST['cart_item_key'] )
{
WC()->cart->remove_cart_item($cart_item_key);
}
}
WC()->cart->calculate_totals();
WC()->cart->maybe_set_cart_cookies();
woocommerce_mini_cart();
$mini_cart = ob_get_clean();
// Fragments and mini cart are returned
$data = array(
'fragments' => apply_filters( 'woocommerce_add_to_cart_fragments', array(
'div.widget_shopping_cart_content' => '<div class="widget_shopping_cart_content">' . $mini_cart . '</div>'
)
),
'cart_hash' => apply_filters( 'woocommerce_add_to_cart_hash', WC()->cart->get_cart_for_session() ? md5( json_encode( WC()->cart->get_cart_for_session() ) ) : '', WC()->cart->get_cart_for_session() )
);
wp_send_json( $data );
die();
}
add_action( 'wp_ajax_product_remove', 'warp_ajax_product_remove' );
add_action( 'wp_ajax_nopriv_product_remove', 'warp_ajax_product_remove' );It removes the cart item whose product ID and key match the request and recalculates the totals. Then it returns WooCommerce’s standard mini cart as the fragment for div.widget_shopping_cart_content. If your mini cart uses different markup, add your own fragment through the woocommerce_add_to_cart_fragments filter so it refreshes too, the same way a header cart count is refreshed with cart fragments. The handler doesn’t check a nonce, so consider adding one.
Need the whole cart flow, from mini cart to checkout, built into a custom theme instead? See my WooCommerce development services.
Related articles
- overriding WooCommerce’s mini-cart.php template: custom item markup in the default mini cart instead of your own loop

