Updated Sep.10, 2025
The previous code has bugs, as my client said “I noticed a glitch where if I go to the cart page after clicking “View Cart” from the product page(1st screenshot) it doesn’t show the “spend xx more to get free shipping” message(2nd screenshot).When I updated the quantity, then it showed up(3rd screenshot). Can you look into this?”
Here’s the updated one:
/**
* Free shipping threshold notice.
*
* On the cart+checkout show a 'Spend $x more to get free shipping!' notice.
*
* Related post: https://aceplugins.com/how-to-add-a-free-shipping-threshold-notice-in-woocommerce/
*/
function ace__free_shipping_threshold_notice() {
// Only run on cart and checkout pages
if ( ! is_cart() && ! is_checkout() ) {
return;
}
// Additional check to ensure we're not on admin pages or other contexts
if ( is_admin() || ! function_exists( 'WC' ) || ! WC()->cart ) {
return;
}
// Let JavaScript handle duplicate prevention instead of PHP static variables
// Check if we already have a free shipping notice to prevent duplicates
$notices = wc_get_notices( 'notice' );
$has_free_shipping_notice = false;
if ( ! empty( $notices ) ) {
foreach ( $notices as $notice ) {
if ( strpos( $notice['notice'], 'free shipping' ) !== false ) {
$has_free_shipping_notice = true;
break;
}
}
}
// If we already have a notice, return early
if ( $has_free_shipping_notice ) {
return;
}
$packages = WC()->cart->get_shipping_packages();
$package = reset( $packages );
$zone = wc_get_shipping_zone( $package );
$cart_total = WC()->cart->get_displayed_subtotal();
$deduct = ( WC()->cart->get_discount_total() + ( WC()->cart->display_prices_including_tax() ? WC()->cart->get_discount_tax() : 0 ) );
$cart_total = round( $cart_total - $deduct, wc_get_price_decimals() );
foreach ( $zone->get_shipping_methods( true ) as $method ) {
if ( $method->id !== 'free_shipping' ) continue;
$min_amount = $method->get_option( 'min_amount' );
if ( ! empty( $min_amount ) ) {
if ( $cart_total < $min_amount ) {
// Cart total is below threshold - show notice
$remaining = $min_amount - $cart_total;
// Create a dismissible notice with close button
$message = sprintf(
'<div id="free-shipping-notice" style="position: relative; padding-right: 30px; display: flex; align-items: center; justify-content: space-between; cursor: pointer;">'
. '<span>Spend %s more to get free shipping!</span>'
. '<button type="button" style="'
. 'background: transparent; border: none; '
. 'width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; '
. 'cursor: pointer; color: rgba(0,0,0,0.4); font-size: 22px; font-weight: normal;font-weight:900; '
. 'transition: all 0.2s ease; margin-left: 10px; flex-shrink: 0; '
. 'border-radius: 3px; line-height: 1;'
. '" onmouseover="this.style.background=\'rgba(0,0,0,0.1)\'; this.style.color=\'rgba(0,0,0,0.7)\'" '
. 'onmouseout="this.style.background=\'transparent\'; this.style.color=\'rgba(0,0,0,0.4)\'" '
. 'title="关闭此通知">×</button></div>',
wc_price( $remaining )
);
wc_add_notice( $message, 'notice' );
} else {
// Cart total meets or exceeds threshold - remove any existing free shipping notices
$notices = wc_get_notices( 'notice' );
if ( ! empty( $notices ) ) {
$updated_notices = array();
foreach ( $notices as $notice ) {
if ( strpos( $notice['notice'], 'free shipping' ) === false ) {
$updated_notices[] = $notice;
}
}
// Clear all notices and re-add only non-free-shipping ones
wc_clear_notices();
foreach ( $updated_notices as $notice ) {
wc_add_notice( $notice['notice'], 'notice' );
}
}
}
break; // Only process one free shipping method
}
}
}
// Clear the static flag when cart is updated so notice can show again if needed
function ace__clear_free_shipping_notice_flag() {
// Static variables are automatically reset on new requests
// This function is kept for potential future enhancements
}
// Add JavaScript for dismissing the notice and preventing duplicates
function ace__free_shipping_notice_script() {
// Load script on all pages to handle notice removal on non-cart/checkout pages
if ( ! is_admin() ) {
?>
<script type="text/javascript">
// Track if free shipping notice has been added
window.freeShippingNoticeAdded = window.freeShippingNoticeAdded || false;
document.addEventListener('DOMContentLoaded', function() {
// Check if we're on cart or checkout page
var isCartPage = document.body.classList.contains('woocommerce-cart') ||
document.body.classList.contains('page-id-cart') ||
window.location.href.indexOf('/cart') !== -1;
var isCheckoutPage = document.body.classList.contains('woocommerce-checkout') ||
document.body.classList.contains('page-id-checkout') ||
window.location.href.indexOf('/checkout') !== -1;
// Remove free shipping notices if not on cart/checkout pages
if (!isCartPage && !isCheckoutPage) {
jQuery('.woocommerce-info').each(function() {
if (jQuery(this).text().indexOf('free shipping') !== -1) {
jQuery(this).remove();
}
});
return; // Exit early if not on cart/checkout
}
// Remove duplicate notices (both free shipping and other WooCommerce notices)
function removeDuplicateNotices() {
var notices = document.querySelectorAll('.woocommerce-info, .woocommerce-message, .woocommerce-error');
var seenNotices = {};
notices.forEach(function(notice) {
var noticeText = notice.textContent.trim();
if (seenNotices[noticeText]) {
notice.remove();
} else {
seenNotices[noticeText] = true;
}
});
}
// Run on page load
removeDuplicateNotices();
// Run after AJAX updates - handle cart changes dynamically without page reload
jQuery(document.body).on('updated_checkout updated_cart_totals', function() {
setTimeout(function() {
// Remove duplicate notices after AJAX updates
removeDuplicateNotices();
// Check if cart is empty
var cartEmpty = jQuery('.cart-empty').length > 0 ||
jQuery('.woocommerce-info:contains("Your cart is currently empty")').length > 0 ||
jQuery('body').hasClass('woocommerce-cart-empty');
// If cart is empty, hide free shipping notices
if (cartEmpty) {
jQuery('.woocommerce-info').each(function() {
if (jQuery(this).text().indexOf('free shipping') !== -1) {
jQuery(this).remove();
}
});
} else {
// Check if threshold is met by looking for "Congratulations" or absence of "Spend" message
// If no "Spend more" notice exists but we have other notices, threshold might be met
var hasSpendMoreNotice = false;
jQuery('.woocommerce-info').each(function() {
if (jQuery(this).text().indexOf('Spend') !== -1 && jQuery(this).text().indexOf('free shipping') !== -1) {
hasSpendMoreNotice = true;
}
});
// If cart is not empty but no "spend more" notice, remove any lingering free shipping notices
if (!hasSpendMoreNotice) {
jQuery('.woocommerce-info').each(function() {
if (jQuery(this).text().indexOf('free shipping') !== -1) {
jQuery(this).remove();
}
});
}
}
}, 200);
});
// Handle notice dismissal
document.addEventListener('click', function(e) {
var target = e.target;
var woocommerceInfo = target.closest('.woocommerce-info');
if (woocommerceInfo && woocommerceInfo.innerHTML.indexOf('free shipping') !== -1) {
woocommerceInfo.style.display = 'none';
}
});
});
</script>
<?php
}
}
add_action( 'wp_footer', 'ace__free_shipping_notice_script' );
// Clear notice flags when cart changes to allow fresh notice calculation
add_action( 'woocommerce_cart_updated', 'ace__clear_free_shipping_notice_flag' );
add_action( 'woocommerce_add_to_cart', 'ace__clear_free_shipping_notice_flag' );
add_action( 'woocommerce_cart_item_removed', 'ace__clear_free_shipping_notice_flag' );
// Hook into essential cart and checkout events to ensure the notice shows consistently
add_action( 'woocommerce_before_cart', 'ace__free_shipping_threshold_notice' );
add_action( 'woocommerce_before_checkout_form', 'ace__free_shipping_threshold_notice' );
add_action( 'woocommerce_cart_updated', 'ace__free_shipping_threshold_notice' );
// Removed template_redirect hook to prevent notice from showing on non-cart/checkout pages
————— Old post below

Here’s the code snippet to add a “spend xx more and get free shipping” message on top of cart page or the checkout page:
<?php
// Define a function that will check and display a notice about free shipping threshold
function ace__free_shipping_threshold_notice() {
// Check if we're on the cart page - is_cart() is a WooCommerce function that returns true only on cart page
if ( is_cart() ) {
// WC() is the main WooCommerce class instance
// ->cart accesses the cart object
// ->get_shipping_packages() gets all items in cart that need shipping
$packages = WC()->cart->get_shipping_packages();
// reset() gets the first element of an array
// We only need the first package as shipping is usually calculated per cart
$package = reset( $packages );
// Get the shipping zone based on customer's address
// Shipping zones determine available shipping methods for the customer's location
$zone = wc_get_shipping_zone( $package );
// Get the cart subtotal (before shipping)
$cart_total = WC()->cart->get_displayed_subtotal();
// Calculate any discounts that need to be deducted
// If prices include tax, we also deduct the discount tax
$deduct = ( WC()->cart->get_discount_total() +
( WC()->cart->display_prices_including_tax() ?
WC()->cart->get_discount_tax() : 0 ) );
// Calculate the final cart total after deducting discounts
// wc_get_price_decimals() gets the number of decimal places set in WooCommerce
$cart_total = round( $cart_total - $deduct, wc_get_price_decimals() );
// Loop through all enabled shipping methods in this zone
foreach ( $zone->get_shipping_methods( true ) as $method ) {
// Skip if this is not a free shipping method
if ( $method->id !== 'free_shipping' ) continue;
// Get the minimum amount required for free shipping
$min_amount = $method->get_option( 'min_amount' );
// If there is a minimum amount set AND cart total is less than that
if ( ! empty( $min_amount ) && $cart_total < $min_amount ) {
// Calculate how much more customer needs to spend
$remaining = $min_amount - $cart_total;
// Add a notice to the cart page
// wc_price() formats the price according to store settings
// 'notice' is the type of message (others could be 'error' or 'success')
wc_add_notice( sprintf( 'Spend %s more to get free shipping!',
wc_price( $remaining ) ), 'notice' );
}
}
}
}
// Hook our function to run before the cart is displayed
// 'woocommerce_before_cart' is a WooCommerce action hook that fires at the start of cart page
add_action( 'woocommerce_before_cart', 'ace__free_shipping_threshold_notice' );
It will show like this
