Take a store with free shipping set at $75. Average order value is $68, the average shipping charge is $9, and gross margin is 38%. The threshold looks generous. It is a $6.34 loss on every order that reaches exactly $75 and no further.
The arithmetic is not complicated. The setting is just easy to place by feel, and WooCommerce will happily accept any number you type.
Here is the number that matters: a customer who adds $7 to hit your threshold generates $2.66 in gross profit at 38% margin. You gave up $9 in shipping revenue to get it. The order only works if enough customers add far more than the minimum, or if the offer lifts conversion enough to pay for the gap. Neither is guaranteed, and neither is visible in the orders screen.
What the Setting Actually Does
Free Shipping is a shipping method inside a shipping zone, not a store-wide switch. You add it under WooCommerce → Settings → Shipping → Shipping zones, and it applies only to customers whose address matches that zone. The shipping zones documentation covers the matching rules.
Inside the method, the “Free Shipping Requires” dropdown has five options, documented on the Free Shipping page:
- No requirement. Everyone in the zone ships free.
- A valid free shipping coupon. Only customers who apply a coupon marked “Allow free shipping.”
- A minimum order amount. The threshold most stores mean when they say “free shipping over $X.”
- A minimum order amount OR a coupon. Either path qualifies.
- A minimum order amount AND a coupon. Both are required, which is a very narrow offer.
Two details catch people. First, the minimum is measured on the cart subtotal, not on the order total including shipping. Second, when a minimum is used, a checkbox appears: Apply minimum order rule before coupon discount. Unchecked, WooCommerce checks the threshold after the coupon reduces the cart. Checked, it uses the pre-discount amount.
That checkbox is a pricing decision disguised as a preference. An $80 cart with a 20% coupon drops to $64. Unchecked, the customer loses free shipping; checked, they keep it and the discount stacks on top of the subsidy. Neither is wrong, but you have to choose on purpose.
The Break-Even Threshold
Write the offer as a comparison of two states. Before, an order of size A earns A × m in product margin plus R in shipping revenue, and pays C to the carrier. After, an order of size T earns T × m and pays the same C, but collects no shipping revenue.
The carrier cost cancels out because the parcel still ships:
(T × m) − (A × m) − R ≥ 0
T ≥ A + R / m
T is the break-even threshold, A is the average product subtotal, R is the average shipping charge you currently collect, and m is your gross margin rate. Use product margin, not revenue, and subtract payment fees before you call it gross.
For the store above: 68 + 9 / 0.38 = 91.68. The honest threshold was $92, not $75. At $92, the required basket lift is $24, which is a much harder ask than $7. That is the real trade-off, and it should be visible before you publish the offer.
Run the calculation instead of retyping the formula:
<?php
/**
* Break-even threshold for a free shipping offer.
* Usage: php free-shipping-breakeven.php <aov> <avg_shipping_charge> <margin_rate>
*/
if ( $argc < 4 ) {
fwrite( STDERR, "Usage: php free-shipping-breakeven.php <aov> <avg_shipping_charge> <margin_rate>\n" );
exit( 1 );
}
$aov = (float) $argv[1];
$ship = (float) $argv[2];
$margin = (float) $argv[3];
if ( $aov <= 0 || $ship < 0 || $margin <= 0 || $margin >= 1 ) {
fwrite( STDERR, "AOV must be > 0, shipping >= 0, margin between 0 and 1.\n" );
exit( 1 );
}
$break_even = $aov + ( $ship / $margin );
printf( "Current AOV: %8.2f\n", $aov );
printf( "Average shipping paid: %8.2f\n", $ship );
printf( "Gross margin rate: %8.2f%%\n", $margin * 100 );
printf( "Break-even threshold: %8.2f\n", $break_even );
printf( "Required basket lift: %8.2f\n", $break_even - $aov );
foreach ( array( 0.25, 0.30, 0.35, 0.40, 0.45, 0.50 ) as $m ) {
printf( "margin %.0f%% -> threshold %.2f\n", $m * 100, $aov + $ship / $m );
}
The sensitivity loop is the useful part. If your margin estimate is off by five points, the threshold moves by several dollars, and you can see whether the offer is close to viable or nowhere near it.
The same arithmetic as a table, for a store with a $60 subtotal and $8 in average shipping revenue:
| Gross margin | Required basket lift | Break-even threshold |
|---|---|---|
| 25% | $32.00 | $92.00 |
| 30% | $26.67 | $86.67 |
| 35% | $22.86 | $82.86 |
| 40% | $20.00 | $80.00 |
| 45% | $17.78 | $77.78 |
At 25% margin, the offer demands a 53% increase in basket size. At 45%, it demands 30%. Same shipping charge, very different offer.
That formula prices one marginal order — the customer who would not have reached the threshold without the offer. It does not account for orders that already clear the threshold, which get the subsidy with no basket change at all.
The Orders That Were Already Over the Line
Take 100 orders at the $92 threshold, with 40 of them already above it. Those 40 cost $9 each in forgone shipping revenue: $360. Of the 60 below the line, suppose 15 add enough to qualify, and the average lift is $20. The extra margin is 15 × 20 × 0.38 = $114. The offer has given up 55 × 9 = $495 in shipping revenue. Net: −$381 before any conversion effect.
For the 15 lifters to pay for the whole program, each would need to add 495 / (15 × 0.38) = $86.84, not $23.68. That is the number that makes store owners abandon the idea, and at a 38% margin it is usually the right reaction.
The decisive variable is the share of orders that already qualify. If only 10% clear the threshold, the fixed subsidy is small and the marginal math dominates. If 60% clear it, most of the budget goes to orders the offer never influenced.
Conversion lift can change the conclusion, but it has to be measured. That is what the zone test later in this article is for.
Why One National Threshold Usually Fails
A single threshold assumes shipping costs the same everywhere. It rarely does. A zone covering your home state might cost $6 per parcel; a zone covering remote regions might cost $18 for the same box.
Set the threshold per zone, using that zone’s carrier cost and the shipping revenue you collect there. With a 40% margin and $6, $11, and $18 in average shipping revenue, the break-even points are $15, $27.50, and $45 above the local average subtotal. One national number will overcharge the cheap zone and subsidize the expensive one.
Carrier pricing is also dimensional. A light but bulky item can cost more to ship than a heavy compact one, which is why the parcel’s billable weight matters more than the product weight on the listing. The logistics integrations guide covers how carrier rates and fulfilment data connect; the point here is that your shipping revenue per zone is an input to the threshold, not a guess.
If the numbers per zone are not available, start with two zones rather than one: domestic and everywhere else. Two honest numbers beat one national average.
The Coupon Stacking Trap
The checkbox described earlier is where most of the leakage happens, and it interacts with the requirement mode in ways that are easy to miss.
With minimum order amount OR a coupon, a customer holding a free-shipping coupon bypasses the threshold entirely. If that coupon is also 15% off, the store pays the shipping subsidy and the discount on a cart that may be far below the threshold. The coupon management documentation explains the free-shipping flag; the economics are yours to check.
With minimum order amount AND a coupon, the customer needs both, which usually means the threshold is irrelevant because the coupon already did the persuading.
Test the exact combination before launch. Add a product to reach just over the threshold, apply the coupon you plan to run, and watch whether the free option survives. Then repeat with the checkbox in the other state. This takes five minutes and prevents a month of silent subsidy.
One policy that holds up: keep the checkbox checked when the coupon is a small thank-you for a loyal customer, and unchecked when the coupon is a broad acquisition discount. The first is a reward on top of a full-price order. The second is a discount on an order that may no longer deserve free shipping.
A Free Shipping Coupon Is a Different Offer
A coupon with “Allow free shipping” enabled is a targeted subsidy, not a threshold. It can be the right tool for a win-back campaign or a newsletter signup, because you know who receives it and you can cap the number of uses.
It is the wrong tool for rescuing a threshold that does not work. If the break-even math says $92 and you set $75, adding a coupon that grants free shipping at any amount does not fix the margin; it removes the qualifying condition entirely.
Use the coupon path when the goal is acquisition and you can measure the cohort. Use the threshold when the goal is basket growth and you can measure the lift. Mixing them produces a number that cannot be attributed to either.
Measure the Basket Before You Change It
You need three numbers from your own orders: average product subtotal, the share of orders that already ship free, and how order values cluster around the proposed threshold.
This script runs under WP-CLI with WooCommerce loaded. Change $threshold to the number you are considering.
<?php
/**
* Run with: wp eval-file measure-free-shipping.php
* Reports order volume, AOV, free-shipping share, and basket clustering.
*/
$threshold = 92.00; // Change to the threshold you are testing.
$days = 90;
$orders = wc_get_orders(
array(
'limit' => -1,
'status' => array( 'processing', 'completed' ),
'date_created' => '>' . ( time() - ( $days * DAY_IN_SECONDS ) ),
)
);
if ( empty( $orders ) ) {
WP_CLI::error( 'No paid orders in the window.' );
}
$count = 0;
$total = 0.0;
$free = 0;
$buckets = array(
'below_90' => 0,
'within_10' => 0,
'at_or_above' => 0,
);
foreach ( $orders as $order ) {
$count++;
// Subtotal after discounts, matching the default checkbox state.
// Use get_subtotal() alone if your store applies the minimum before coupons.
$order_subtotal = (float) $order->get_subtotal() - (float) $order->get_discount_total();
$total += $order_subtotal;
$has_free = false;
foreach ( $order->get_shipping_methods() as $method ) {
if ( 'free_shipping' === $method->get_method_id() ) {
$has_free = true;
break;
}
}
if ( $has_free ) {
$free++;
}
if ( $order_subtotal >= $threshold ) {
$buckets['at_or_above']++;
} elseif ( $order_subtotal >= $threshold * 0.9 ) {
$buckets['within_10']++;
} else {
$buckets['below_90']++;
}
}
WP_CLI::log( sprintf( 'Orders: %d', $count ) );
WP_CLI::log( sprintf( 'AOV (product subtotal): %.2f', $total / $count ) );
WP_CLI::log( sprintf( 'Free-shipping share: %.1f%%', ( $free / $count ) * 100 ) );
WP_CLI::log( sprintf( 'Below 90%% of threshold: %d', $buckets['below_90'] ) );
WP_CLI::log( sprintf( 'Within 10%% below threshold: %d', $buckets['within_10'] ) );
WP_CLI::log( sprintf( 'At or above threshold: %d', $buckets['at_or_above'] ) );
Run it once now, then again 90 days after any change:
wp eval-file measure-free-shipping.php
The output is not a proof of causation. It is a baseline. If the free-shipping share is already 70%, most customers qualify without trying, and the threshold is doing very little. If it is 5%, the offer is either invisible or too far away.
Read the Histogram, Not the Average
The bucket counts tell you whether the threshold is shaping behavior. Two patterns are worth acting on.
A pile-up just above the threshold. Customers are adding a small item to qualify. That is the basket lift you are paying for, and the question is whether the added margin covers the shipping subsidy. Compare the average added value with the break-even lift.
A gap just below the threshold. Customers stop short rather than add. That can mean the shipping fee is deterring them, or that the remaining gap is too large to bridge with an impulse item. A smaller threshold may convert more orders, but only if the per-order economics still work at that level.
No pattern at all. The threshold is not salient. It may be buried in the cart, worded vaguely, or contradicted by a paid option that looks faster and safer. Moving the number without fixing the presentation wastes the test.
Judge the result over a full quarter. Shipping behavior has weekly and seasonal cycles, and a two-week comparison will mostly measure which week had a sale.
Hide Paid Rates Only When You Mean It
By default, WooCommerce shows every rate that matches the package. A customer who qualifies for free shipping still sees flat rate and express options. Sometimes that is right: someone who needs the parcel tomorrow should be able to pay for it.
When you want free shipping to be the only visible option, WooCommerce’s developer documentation provides a filter on woocommerce_package_rates. The free shipping customizations page has the canonical snippet:
add_filter(
'woocommerce_package_rates',
function ( $rates ) {
foreach ( $rates as $rate_id => $rate ) {
if ( 'free_shipping' === $rate->method_id ) {
return array( $rate_id => $rate );
}
}
return $rates;
},
10,
1
);
The trade-off is real. Hiding express shipping removes a choice that some customers will pay for, and it can push a customer who needed speed to abandon instead of upgrading. Keep paid rates visible unless the free option is genuinely as good for that zone.
Exclude Oversized Items From the Offer
A threshold that ignores parcel size invites the worst possible order: the customer adds a bulky item to reach the minimum, and the shipping cost exceeds the entire margin.
WooCommerce shipping classes are the right place to mark those products. Create a class for oversized or freight items, assign it to the products, and remove free shipping when the package contains one. The shipping classes documentation covers creating and assigning them.
add_filter(
'woocommerce_package_rates',
function ( $rates, $package ) {
foreach ( $package['contents'] as $item ) {
$product = $item['data'];
if ( ! $product || 'oversized' !== $product->get_shipping_class() ) {
continue;
}
foreach ( $rates as $rate_id => $rate ) {
if ( 'free_shipping' === $rate->method_id ) {
unset( $rates[ $rate_id ] );
}
}
break;
}
return $rates;
},
10,
2
);
Replace oversized with your shipping class slug. The slug is visible in Products → Shipping classes; it is usually the class name lowercased with hyphens.
Two notes on behaviour. A shipping class only affects the rate calculation if a shipping method uses it. Free Shipping ignores classes by default, which is exactly why the filter is needed. And if your store splits a cart into multiple packages, run the check per package, not per order.
Verify the Setting, Not the Screenshot
The settings screen can disagree with what the store actually uses, especially after a migration, a staging push, or a plugin that manages shipping. The values live in options named woocommerce_free_shipping_<instance_id>_settings.
List them and read one:
wp option list --search='woocommerce_free_shipping_*_settings' --fields=option_name
wp option get woocommerce_free_shipping_1_settings --format=json
The JSON shows requires, min_amount, and ignore_discounts. That last key is the coupon checkbox: no means the threshold is checked after discounts, yes means before. If the store has three zones, there are three instances, and they can hold three different thresholds — which is the point, but also a reason to check every one.
Clear WooCommerce’s cached shipping rates after any change. The documentation points to WooCommerce → Status → Tools → WooCommerce transients → Clear transients. A stale rate cache will show the old threshold to customers who tested before the change.
When Free Shipping Should Not Be a Threshold
Not every store should run this offer. Three cases where a flat rate is the better answer:
Low margin, heavy goods. If the break-even threshold lands far above what customers will spend, the offer cannot pay for itself. Charge a real rate and compete on something other than shipping.
Very wide product range. A store selling both phone cases and furniture cannot use one threshold without losing money on half the catalog. Split by shipping class or run the offer only on the lightweight range.
Customers who need speed. If your value proposition is next-day delivery, free standard shipping at a threshold is a distraction. Make the paid option the default and keep the free tier for orders that can wait.
The payment side matters too. A free-shipping offer that saves the customer $9, followed by a checkout that fails on a card decline, is not a conversion strategy. The payment system guide and the checkout speed guide are worth reading before you spend margin on shipping.
A Threshold Change Is a Pricing Change
Treat the rollout like a price change, because that is what it is.
- Set the number per zone. Use that zone’s average shipping revenue and margin, not the site-wide average.
- Decide the coupon policy. Check or uncheck “Apply minimum order rule before coupon discount” and test both the threshold path and the coupon path.
- Exclude the products that break the model. Shipping classes plus the rate filter, not a manual review of every order.
- Record the baseline. Order count, AOV, free-shipping share, and the bucket counts, dated.
- Change one zone first. A single zone gives you a comparison group that the rest of the store provides for free.
- Re-measure at 90 days. If the lift does not cover the subsidy, move the threshold up or drop the offer.
The last step is the one people skip. Without it, the threshold becomes a permanent discount that nobody remembers deciding.
For stores where basket size is the real problem, free shipping is only one lever. Product page structure, cross-sells, and attribute-driven navigation all change what lands in the cart. The product attributes guide and the conversion-focused theme guide cover the parts that happen before the shipping line. The ecommerce sales guide puts the whole funnel in order.
FAQ
What is a good free shipping threshold?
One that sits above your break-even point: average product subtotal plus average shipping revenue divided by gross margin rate. If that number is far above what customers typically spend, the offer is not viable at your current margins, and a lower threshold will lose money faster.
Does free shipping increase sales?
It can increase conversion and basket size, but the size of the effect depends on your customers, your shipping fee, and what competitors charge. There is no universal lift you can plug into a forecast. Measure a single zone against the others before rolling it out.
Should the minimum be calculated before or after a coupon?
Either can be right. Checking after the discount protects margin when coupons are broad. Checking before the discount treats free shipping as a loyalty reward and lets the coupon stack on top. Pick one policy, set the checkbox accordingly, and test the exact coupon you plan to run.
Can I set a different threshold for each shipping zone?
Yes. Free Shipping is a method inside a zone, so each zone has its own instance and its own minimum. That is usually the correct setup, because carrier cost and average order value differ by region.
Is the threshold based on the cart subtotal or the order total?
The cart subtotal, excluding shipping. Tax treatment depends on your price display settings. Do not compare the threshold with an order total that includes shipping revenue, because that inflates the apparent basket size.
How do I exclude heavy or oversized items?
Create a shipping class, assign it to those products, and remove the free-shipping rate when the package contains a product in that class. Free Shipping does not respect shipping classes on its own.
Why is free shipping not showing at checkout?
Check four things in order: the customer’s address matches the zone, the Free Shipping method is enabled in that zone, the cart subtotal meets the minimum after any coupon, and the rate cache has been cleared. A staging site with different zone settings is the fifth, less common cause.
Should I hide the paid shipping options when free shipping is available?
Only if the free option is genuinely better for that customer. Hiding express rates removes a choice people pay for and can cost more in abandoned carts than it saves in subsidies. Most stores should leave the paid rates visible.