Add a free shipping progress notice to WooCommerce cart: a plugin you can build today
· 5 min read
WooCommerce does not ship with a free shipping progress notice. The cart shows totals and available shipping methods and nothing else. Whether a customer is fifty cents short of your free shipping threshold or fifty dollars short, the cart looks identical.
The question is whether you need a plugin for this or whether a snippet of PHP and CSS will do. If a static notice that updates only on full page reload is enough, a snippet works fine. If you want it to recalculate immediately when a customer adds or removes an item from the mini-cart, without reloading the page, you need JavaScript in the picture, and a small plugin is the cleanest way to deliver that.
Most of the free shipping bar plugins on the directory solve exactly this problem but arrive with style builders, multiple display locations, and integration hooks for conditional shipping rules. If your setup is one threshold, one message, and standard WooCommerce, none of that is needed.
What one build gives you
- A settings field in WooCommerce Shipping settings for the free shipping threshold amount
- A live notice in the cart totals area showing the exact amount remaining to qualify
- A mini-cart notice that updates via AJAX when items are added or removed
- A confirmation message when the cart total meets the threshold
What it does not do
- Automatic sync with the minimum order amount set in WooCommerce Shipping zone methods
- Per-zone thresholds: one global amount applies to all customers regardless of region
- Animated or progress-bar display: the notice is a plain text line below the cart subtotal
- Compatibility with conditional free shipping plugins that recalculate eligibility dynamically
- Display on product pages or outside the cart and mini-cart widget
Free Shipping Bar does these. This covers the part most sites use.
The prompt
Loads into the composer so you can edit it first. Nothing is built, and nothing is charged, until you send it.
Build a WooCommerce plugin called Free Shipping Notice for WooCommerce. Plugin Name: Free Shipping Notice for WooCommerce. Description: Shows cart customers how close they are to the free shipping threshold. Single PHP file. No dependencies beyond WooCommerce. Add a settings field to the Shipping tab of WooCommerce Settings using the woocommerce_shipping_settings filter. Field: id fsn_threshold, type number, title Free shipping from, desc Enter the order subtotal at which free shipping applies., css width: 80px, custom_attributes min:0 step:0.01. The field saves and loads via the standard WooCommerce settings API. Write a private helper fsn_get_data() that reads the fsn_threshold option (floatval, default 0), reads WC()->cart->get_subtotal() (floatval), and returns an array: threshold (float), subtotal (float), remaining (max(0, threshold minus subtotal)), qualified (bool, subtotal >= threshold and threshold > 0.0). Return null if threshold is 0. Write fsn_render_notice() with no parameters. Call fsn_get_data(); return early if null or if WC()->cart is not available. If qualified output: <p id="fsn-notice" class="fsn-notice fsn-qualified">You've qualified for free shipping!</p>. Otherwise output: <p id="fsn-notice" class="fsn-notice">Add [wc_price( $data['remaining'] )] more for free shipping.</p> using wp_kses_post around the wc_price call. Hook fsn_render_notice on woocommerce_cart_totals_after_order_total and woocommerce_mini_cart_contents. Register wp_ajax_fsn_refresh and wp_ajax_nopriv_fsn_refresh. The callback verifies the nonce field fsn_nonce against action fsn_refresh using check_ajax_referer. It calls fsn_get_data(); if null, return wp_send_json_success( array( 'html' => '' ) ). Otherwise build the same notice HTML as fsn_render_notice into a string (ob_start / ob_get_clean pattern) and return wp_send_json_success( array( 'html' => $html ) ). On wp_enqueue_scripts, when is_cart() or is_checkout(), enqueue a script handle fsn-script with an empty src (use wp_add_inline_script instead). Pass to the script via wp_localize_script with object fsnData: keys ajaxurl (admin_url( 'admin-ajax.php' )) and nonce (wp_create_nonce( 'fsn_refresh' )). The inline script (added via wp_add_inline_script to handle fsn-script, position after): on jQuery events added_to_cart, removed_from_cart, updated_cart_totals fired on document: POST to fsnData.ajaxurl with action fsn_refresh and nonce fsnData.nonce. On success, if response.data.html is empty string remove #fsn-notice; else if #fsn-notice exists replace it with response.data.html using .replaceWith(); otherwise append response.data.html to .cart_totals and to .widget_shopping_cart_content. Output inline CSS via wp_head (wp_add_inline_style on a registered style handle): .fsn-notice { display:block; margin:.5em 0; font-size:.9em; color:#555; } .fsn-qualified { color:#4CAF50; font-weight:600; }
Why a PHP snippet does not cover the mini-cart
A snippet using woocommerce_cart_totals_after_order_total renders once when the cart page loads. That covers the customer who lands on the cart and checks how close they are. It does not cover the customer using a mini-cart widget who is adding items without leaving the current page.
WooCommerce mini-cart updates over AJAX. A shipping notice that lives only in PHP output does not update with it. A customer adds an item that tips them over the threshold and the notice still shows the old shortfall amount. The fix is an AJAX endpoint that recalculates the gap and a JavaScript handler that calls it whenever the cart changes.
That combination, a PHP render function plus an AJAX endpoint plus a wp_footer script that listens for cart update events, is a clean plugin job. It is not a large one. The whole thing runs to about a hundred lines.
What the plugin adds to WooCommerce
A settings field in WooCommerce > Settings > Shipping: a single number input for the free shipping threshold. This separates the threshold from the plugin code so the amount can be changed without editing a file.
A notice in the cart totals area and in the mini-cart. Below the subtotal line, the notice shows the remaining amount: Add $12.50 more for free shipping. When the cart meets or exceeds the threshold the message switches to You've qualified for free shipping. The notice does not appear if no threshold is configured.
A nonce-protected AJAX action that recalculates the remaining amount from the live session cart. The JavaScript handler fires on the added_to_cart, removed_from_cart, and updated_cart_totals events and updates the notice in place, without a page reload.
When to use a dedicated plugin instead
WooCommerce's own Free Shipping shipping method has a minimum order amount field. This plugin stores its threshold separately and does not read that field. If you change the amount in your shipping zone settings you will need to update the plugin setting to match. For most stores that is a minor inconvenience, but it is worth knowing before you set it up.
If your store has multiple shipping zones with different free shipping thresholds by region, this plugin is not the right tool. It applies one global threshold to all customers regardless of their location. Zone-aware threshold logic requires reading the active zone per cart session, which is a meaningfully more complex build.
For animated progress bars, percentage fills, or deep integration with conditional free shipping plugins, use a dedicated solution. Free Shipping Bar by WP Desk is a well-maintained free option that covers all of those cases without requiring a custom build.
Questions
- Does the threshold sync with the minimum order amount I set in my WooCommerce free shipping method?
No. The plugin stores its own threshold in a separate settings field. If you change the amount in your WooCommerce shipping zone settings, update the plugin field to match.
- Does the remaining amount reflect coupon discounts?
Yes. The plugin reads the cart subtotal after WooCommerce has applied coupons and discounts, so the amount shown is the real gap between the discounted total and the threshold.
- Can I show the notice on product pages as well?
Not in this build: the notice renders in the cart totals block and the mini-cart widget. To show it elsewhere, call the fsn_render_notice function from a template hook in your theme, or ask for a version with a shortcode.
- What happens if I leave the threshold field blank?
The notice does not appear. A zero or empty threshold means the plugin has nothing to calculate, so it exits early and outputs nothing.
The prompt
Loads into the composer so you can edit it first. Nothing is built, and nothing is charged, until you send it.
Build a WooCommerce plugin called Free Shipping Notice for WooCommerce. Plugin Name: Free Shipping Notice for WooCommerce. Description: Shows cart customers how close they are to the free shipping threshold. Single PHP file. No dependencies beyond WooCommerce. Add a settings field to the Shipping tab of WooCommerce Settings using the woocommerce_shipping_settings filter. Field: id fsn_threshold, type number, title Free shipping from, desc Enter the order subtotal at which free shipping applies., css width: 80px, custom_attributes min:0 step:0.01. The field saves and loads via the standard WooCommerce settings API. Write a private helper fsn_get_data() that reads the fsn_threshold option (floatval, default 0), reads WC()->cart->get_subtotal() (floatval), and returns an array: threshold (float), subtotal (float), remaining (max(0, threshold minus subtotal)), qualified (bool, subtotal >= threshold and threshold > 0.0). Return null if threshold is 0. Write fsn_render_notice() with no parameters. Call fsn_get_data(); return early if null or if WC()->cart is not available. If qualified output: <p id="fsn-notice" class="fsn-notice fsn-qualified">You've qualified for free shipping!</p>. Otherwise output: <p id="fsn-notice" class="fsn-notice">Add [wc_price( $data['remaining'] )] more for free shipping.</p> using wp_kses_post around the wc_price call. Hook fsn_render_notice on woocommerce_cart_totals_after_order_total and woocommerce_mini_cart_contents. Register wp_ajax_fsn_refresh and wp_ajax_nopriv_fsn_refresh. The callback verifies the nonce field fsn_nonce against action fsn_refresh using check_ajax_referer. It calls fsn_get_data(); if null, return wp_send_json_success( array( 'html' => '' ) ). Otherwise build the same notice HTML as fsn_render_notice into a string (ob_start / ob_get_clean pattern) and return wp_send_json_success( array( 'html' => $html ) ). On wp_enqueue_scripts, when is_cart() or is_checkout(), enqueue a script handle fsn-script with an empty src (use wp_add_inline_script instead). Pass to the script via wp_localize_script with object fsnData: keys ajaxurl (admin_url( 'admin-ajax.php' )) and nonce (wp_create_nonce( 'fsn_refresh' )). The inline script (added via wp_add_inline_script to handle fsn-script, position after): on jQuery events added_to_cart, removed_from_cart, updated_cart_totals fired on document: POST to fsnData.ajaxurl with action fsn_refresh and nonce fsnData.nonce. On success, if response.data.html is empty string remove #fsn-notice; else if #fsn-notice exists replace it with response.data.html using .replaceWith(); otherwise append response.data.html to .cart_totals and to .widget_shopping_cart_content. Output inline CSS via wp_head (wp_add_inline_style on a registered style handle): .fsn-notice { display:block; margin:.5em 0; font-size:.9em; color:#555; } .fsn-qualified { color:#4CAF50; font-weight:600; }
Steem