Skip to content
ThemesIonic — home
WooCommerce

WooCommerce BOGO: Buy One Get One Free Without Breaking the Cart

WooCommerce coupons discount items; they cannot say "buy two, get the cheaper one free". The usual workaround discounts the most expensive item instead, and the fix is either a few lines of fee code or a coupon tool that understands buy X get Y.

5 min read intermediate

Some links on this page are affiliate links: if you buy through one, we may earn a commission at no extra cost to you. It does not change what we recommend; see the editorial policy.

WooCommerce coupons can discount an item, but they cannot say "when you buy two, the cheaper one is free." The three built-in discount types — percentage, fixed cart and fixed product — apply an amount to items that match the coupon's rules. None of them is triggered by a quantity, and none chooses which unit gets the discount. That is why every "BOGO without a plugin" tutorial is a workaround, and why most of them give away the wrong item.

The workaround that discounts the wrong item

The common recipe: a percentage coupon of 100%, restricted to the promoted products, with Limit usage to X items set to 1. Add two shirts, apply the code, one shirt is free.

It looks right until the shirts cost different amounts. Before applying a coupon, WooCommerce sorts the cart's items by unit price, highest first, and the item limit is filled in that order. A 100% coupon limited to one item therefore makes the most expensive unit free. The usual BOGO rule is the opposite: the cheaper item is free, or "buy one, get one of equal or lesser value".

There are other gaps:

  • The coupon applies whether or not the customer bought two. One shirt in the cart with the code is simply one free shirt, unless you add a minimum spend, which is not the same as a minimum quantity.
  • Customers must enter a code. Automatic promotions need something else.
  • "Buy two, get one free" needs a limit of one per three items, which the setting cannot express; a limit is a fixed number.

A fee-based BOGO that picks the cheapest item

For one simple, permanent offer, a negative fee is the most reliable core technique. Fees are recalculated on every totals run, so the discount follows the cart as it changes — the mechanics are described in woocommerce_cart_calculate_fees.

add_action( 'woocommerce_cart_calculate_fees', 'ti_bogo_fee' );

function ti_bogo_fee( WC_Cart $cart ) {
	$prices = array();

	foreach ( $cart->get_cart() as $item ) {
		$product_id = $item['product_id'];

		if ( ! has_term( 'bogo', 'product_cat', $product_id ) ) {
			continue;
		}

		$unit = (float) $item['data']->get_price();

		for ( $i = 0; $i < $item['quantity']; $i++ ) {
			$prices[] = $unit;
		}
	}

	$free = intdiv( count( $prices ), 2 );

	if ( $free < 1 ) {
		return;
	}

	sort( $prices );
	$discount = array_sum( array_slice( $prices, 0, $free ) );

	$cart->add_fee( __( 'Buy one get one free', 'ti' ), -$discount, true );
}

Every unit in the bogo category goes into a list, the list is sorted cheapest first, and one unit per pair is free. Change 2 to 3 in intdiv() for "buy two, get one free".

Three things to know about this approach:

  1. The fee is not a coupon. Coupon reports, usage limits and "individual use" settings ignore it. A store coupon applied on top still works, which may or may not be what you intend.
  2. Tax ignores the third argument. For a negative fee WooCommerce splits the tax reduction across the cart's tax classes in proportion, whatever taxable says. The amount is treated as excluding tax, so with prices entered including tax the discount comes out larger than the free item; check this on a real order.
  3. Prices are read as stored. If another plugin or your own code changes cart prices, it must do so earlier, in woocommerce_before_calculate_totals, so the fee sees the final price.

When code stops being enough

The snippet is one rule for one category. Promotions rarely stay that simple. Marketing will soon want "buy 3 get 1 at 50% off", a free gift added automatically above a spend, a link in a newsletter that applies the offer, limits per customer, and a report showing how often it was used. Each is possible in code; together they are a coupon engine.

That is the point where a dedicated tool makes sense. WebToffee's Smart Coupons for WooCommerce handles buy X get Y offers with the cheapest or most expensive item as the free one, coupons that apply automatically when the cart qualifies, shareable URL coupons, bulk code generation with CSV export, and restrictions by user role, email, location, payment or shipping method, cart value and dates. Check on staging how its offers appear in your coupon reports before relying on them for measurement.

Choosing the rule

Rule Typical use Watch for
Buy one, get one free (same product) Clearance, consumables Customers adding one item and expecting the free one to appear
Buy one, get the cheaper one free (mixed) Category promotions Which item is "cheaper" with variations at different prices
Buy X, get Y at a percentage off Margin-friendly version Rounding on odd quantities
Free gift above a spend Average order value The gift must be in stock and not purchasable separately at zero

Write the rule on the product page in plain language. "Cheapest item free" and "second item free" are different promises, and the cart must keep the one you made.

Interactions with other discounts

WooCommerce applies multiple coupons in parallel by default: each is calculated on the original price. The Calculate coupon discounts sequentially setting makes each coupon apply to the already-discounted price instead. A BOGO combined with a 10% store coupon gives a different total depending on that setting, and on whether the BOGO is a coupon or a fee. Decide whether the offers should stack, then test both orders of applying codes. If a code is rejected when combined, the reasons are listed in WooCommerce coupon not working.

Verify

  1. One qualifying item: no discount.
  2. Two items at different prices: the cheaper one is discounted.
  3. Three items: one discount for "buy one get one", not one and a half.
  4. Add a store-wide coupon and check the total matches the stacking you intended.
  5. Place a test order and check the fee or coupon line, the tax and the order email.

Frequently asked

Not with the built-in coupon types alone. Percentage, fixed cart and fixed product coupons cannot express a condition on quantity. A few lines of code using a negative cart fee can, and that is enough for one simple, permanent offer.
WooCommerce sorts cart items by price, highest first, before applying a coupon, and the item limit is filled in that order. A 100% coupon limited to one item therefore makes the most expensive unit free, which is the opposite of the usual BOGO rule.
Only if it is implemented as a coupon. A negative fee appears as a fee line, not as a discount, so coupon reports and coupon usage limits ignore it. If you need to measure the promotion, use a coupon-based tool or record the fee name in your reports.

Related guides