Posted on

WooCommerce: How to Setup Tiered Shipping Rates by Order Amount

The new shipping zone management that was introduced with Woo 2.6 gives us the chance to add flat rate, free shipping and local pick-up methods by default.

But what if a client requires 3 different rates depending on the order amount (tiered shipping)? For example: “For orders up to $100, shipping = $5; for orders up to $250, shipping = $2; for orders above $500, shipping = free”.

Is this possible without using a plugin? Well, the answer, as usual, is absolutely yes!

1. Tiered Shipping – Shipping Zone Setup

Go to WooCommerce > Settings > Shipping and create your shipping zone. In the example, I will target US customers and add 3 shipping methods to it: Flat Rate, Flat Rate and Free Shipping.

Shipping Zone Setup for Tiered Shipping
Shipping Zone Setup for Tiered Shipping

2. Tiered Shipping – Shipping Methods Setup

Open each one of the shipping methods previously added to the zone and rename them / set them up like this:

  1. Flat Rate #1 > rename to “Orders Below $100” and assign cost = $5
  2. Flat Rate #2 > rename to “Orders Below $250” and assign cost = $2
  3. Free Shipping > select “Requires a minimum order amount” = $500

Here’s one of the method’s setup:

Shipping Method Setup for Tiered Shipping
Shipping Method Setup for Tiered Shipping

3. Tiered Shipping – PHP Snippet

Now we need to “tell” WooCommerce that, based on the order amount, a Flat Rate should be used instead of the other. Only in this way we can show the correct shipping method to the end user.

First, take a note of the unique ID of the two flat rates. They should look look something like “flat_rate:9“. For more info on how to find it, check here: https://businessbloomer.com/woocommerce-disable-free-shipping-if-cart-has-shipping-class/#woocommerce-how-to-find-shipping-method-id-woo-2-6-after-the-introduction-of-the-new-shipping-zones

Second, let’s code! We’ll need to “unset” flat rate #2 if we are under $100, otherwise we’ll require to “unset” flat rate #1.


/**
* @snippet Tiered Shipping Rates | WooCommerce
* @how-to Watch tutorial @ https://businessbloomer.com/?p=19055
* @sourcecode https://businessbloomer.com/?p=21425
* @author Rodolfo Melogli
* @testedwith WooCommerce 3.3.4
*/

add_filter( 'woocommerce_package_rates', 'bbloomer_woocommerce_tiered_shipping', 10, 2 );
  
function bbloomer_woocommerce_tiered_shipping( $rates, $package ) {
  
	$threshold = 100;
	
	if ( WC()->cart->subtotal < $threshold ) {
   		unset( $rates['flat_rate:1'] );
	} else {
		unset( $rates['flat_rate:2'] );
	}
  
	return $rates;
  
}

Where to add this code?

You can place PHP snippets at the bottom of your child theme functions.php file (before "?>" if you have it). CSS, on the other hand, goes in your child theme style.css file. Make sure you know what you are doing when editing such delicate files - if you need more guidance, please take a look at my free WooCommerce Customization video tutorial.

Does this snippet still work?

Please let me know in the comments if everything worked as expected. I would be happy to revise the snippet if you report otherwise (please provide screenshots). I have tested this code with Storefront theme, the WooCommerce version listed above and a WordPress-friendly hosting on PHP 7.

If you think this code saved you time & money, please join other Business Bloomer supporters and avail of 365 days of WooCommerce benefits. Thank you in advance :)

Need Help with WooCommerce?

Check out these free videos, tutorials and tips!

  • how-to-edit-woocommerce-with-php-snippets
  • woocommerce-hooks-add_action-list-visual
  • woocommerce-customize-single-product-page-PHP
  • woocommerce-customize-shop-page-PHP
  • woocommerce-advanced-customization
  • how-to-edit-woocommerce-cart
  • woocommerce-customize-checkout-page-PHP
  • woocommerce-email-customization
  • woocommerce-conditional-logic

Rodolfo Melogli

Author, WooCommerce expert, WordCamp speaker and Internet marketer, Rodolfo Melogli has worked as a WooCommerce freelancer since 2011. He helps entrepreneurs and developers overcome their WooCommerce nightmares :) Rodolfo is the organiser of WordCamp Dublin, the Dublin WooCommerce Meetup, the Dublin Ecommerce Meetup and the Dublin WordPress Meetup. He enjoys interacting with people, travelling and chasing tennis & soccer balls. Of course, he loves pizza too.

46 thoughts on “WooCommerce: How to Setup Tiered Shipping Rates by Order Amount

  1. I’m struggling with shipping fees right now. I already have states and cities enabled, the problem now is: how to add cities into the calculation. I mean, if a customer is buying from city X (state A) fee is going to be $5, but if he purchases from city Y ( State A), fee would be $9. Do you have any idea on how to do it?

    1. Hello Yara, thanks so much for your comment! Yes, this is possible – but unfortunately this is custom work and I cannot provide a complementary solution here via the blog comments. Thanks a lot for your understanding! ~R

  2. Here is the code I added for three different shipping options based on quantity in the cart:

    
    // Change shipping based on quantity purchased.
    add_filter( 'woocommerce_package_rates', 'bbloomer_woocommerce_tiered_shipping', 10, 2 );
    function bbloomer_woocommerce_tiered_shipping( $rates, $package ) {
    $threshold1 = 2; 
    $threshold2 = 3;
    if ( WC()->cart->get_cart_contents_count() < $threshold1 ) {
    unset( $rates['flat_rate:6'], $rates['flat_rate:8'] );
    } elseif ( WC()->cart->get_cart_contents_count() < $threshold2 ){
    unset( $rates['flat_rate:1'], $rates['flat_rate:8'] ); 
    } else {
    unset( $rates['flat_rate:1'], $rates['flat_rate:6'] );
    } 
    return $rates; 
    }
    
    

    I made this tutorial: https://www.easywebdesigntutorials.com/adjust-shipping-price-when-quantity-changes/

    1. Excellent, thanks Paal!

  3. Hi there, When I post the php code for this the latest version of WordPress rejects it with multiple ‘Unexpected Token’ errors. Ive double checked all the ‘ and , punctuation and it seems fine.
    Can anyone suggest anything that may be causing this please?

    Here is a screengrab of the errors: https://www.dropbox.com/s/el65759iea14zqh/Unexpected%20Token%20errors.png?dl=0

    Thanks in advance! 🙂

    1. Sorry, I was putting the code into the css, I’ve tried it in the functions and now I get:

      Your PHP code changes were rolled back due to an error on line 28 of file wp-content/themes/uk-cbd-oil/functions.php. Please fix and try saving again.
      syntax error, unexpected ‘add_filter’ (T_STRING)

      Here is a screengrab again 🙂
      https://www.dropbox.com/s/fc5ihk2x9qidugw/string_error.png?dl=0

    2. Hey Brian, that’s the weirdest PHP error I’ve ever seen 🙂 Do you have this at the beginning of the file:

      <?php
      
  4. Rodolfo,
    Very helpful snippet and I almost have it working. I have two levels; less than $100, less than $200 then anything over $200 is free shipping. Shipping costs display properly until I exceed $200 in my cart total then both the Free Shipping option and Less than $200 option appears. How do I get rid of the Less than $200 option?
    Thank you.

  5. I’m trying to do something a bit more advanced because my client has three different flat rate tiers as well as two free shipping tiers. Idea is when you hit $100, the free shipping should trigger but also so should a flat rate tier for expedited shipping and the first two flat rate tiers go away. This isn’t working for me, but am I at least in the right ballpark?

    /**
    * @snippet Tiered Shipping Rates | WooCommerce
    * @how-to Watch tutorial @ https://businessbloomer.com/?p=19055
    * @sourcecode https://businessbloomer.com/?p=21425
    * @author Rodolfo Melogli
    * @testedwith WooCommerce 2.6.8, WordPress 4.7
    */
     
    add_filter( 'woocommerce_package_rates', 'bbloomer_woocommerce_tiered_shipping', 5, 2 );
       
    function bbloomer_woocommerce_tiered_shipping( $rates, $package ) {
      $threshold = 100;
        if ( WC()->cart->subtotal < $threshold ) { 
            if ( isset( $rates['flat_rate:1'] ) ) unset( $rates['flat_rate:3'], $rates['free_shipping:1'], $rates['free_shipping:2'] );  
        } else {
            if ( isset( $rates['flat_rate:3'] ) ) unset( $rates['flat_rate:1'], $rates['flat_rate:2'] );   
        }
      return $rates;
    }
    1. Hey Adam, thanks so much for your comment! I don’t see anything wrong with this, try emptying the Cart before testing again 🙂

  6. Sorry i posted on the wrong post. This code works great on woocomerce 3.2.6 and WordPress 4.9.1. Also in theme-functions.php file. i had to add some custom code of mine also but it works like a charm.
    Thank you guys, i really appreciate

  7. I was wondering if there is a way to charge 2.50 if an order is over $300 for the signature required but have customers be able to waive the signature and 2.50 charge if they don’t want to sign for it. is this possible?

    1. Hello Connor, thanks so much for your comment! Yes, this is possible – but unfortunately this is custom work and I cannot provide a complementary solution here via the blog comments. Thanks a lot for your understanding! ~R

  8. Hi is there anyway to simply change the free shipping trigger to occur based on the subtotal (i.e. not effected by he use of coupons) rather than the order total?

    Thanks
    Z

    1. Zed, thanks for your comment! Yes, sure, you can do that. You just need to find the correct order total function: https://docs.woocommerce.com/wc-apidocs/class-WC_Order.html

  9. Is it possible to assign both the country and total suborder based shipping cost in this php code. Please help me

    1. Hey Gopinathan, thanks so much for your comment! Yes, this is possible – but unfortunately this is custom work and I cannot provide a complementary solution here via the blog comments. Thanks a lot for your understanding! ~R

  10. Hi Rodolfo, how much would you charge for implementing this for me?

    1. Wesley, thanks so much for your comment! If you’d like to get a quote, feel free to contact me here. Take care 🙂

    2. Done, thank you sir.

  11. Thank you, this was super handy! Worked absolutely perfectly!

  12. Hi Rodolfo,
    I am new to php and I’m looking to create different “thresholds” to different shipping zones. For example, shipping zone A needs a minimum spend of 100 in order to get free shipping; and shipping zone B needs to spend 200 for free shipping. How would I implement this into the code you provided? Is it even possible lol. Thank you so much!

    1. Hey Thy, thanks for your comment! Yes, of course this is possible, but I’m afraid it’s custom work and I can’t help via the blog comments. Thank you 🙂

  13. Sorry, I didn’t wrap the php

    $threshold1 = 200;
    $threshold2 = 400;
    $threshold3 = 800;
    
     
    if ( WC()->cart->subtotal < $threshold1 ) {
     
    unset( $rates['flat_rate:2'], $rates['flat_rate:6'], $rates['flat_rate:7'], );
     
    } elseif ( WC()->cart->subtotal < $threshold2 ) { 
     
    unset( $rates['flat_rate:1'], $rates['flat_rate:6'], $rates['flat_rate:7'], );
     
    } elseif ( WC()->cart->subtotal < $threshold3 ) { 
     
    unset( $rates['flat_rate:1'], $rates['flat_rate:2'], $rates['flat_rate:7'], );  
     
    }
     
    return $rates;
    
    1. Hey Jamie, thanks so much for your comment! Yes, this is possible – but unfortunately this is custom troubleshooting work and I cannot provide a complementary solution here on the blog. Thanks a lot for your understanding! ~R

    2. I understand. Thank you 🙂

  14. Hello,

    Great article! Really helpful. I’m looking to implement tiered shipping options like this:

    up to £200.00 £ 10.00
    £200.00-£400.00 £ 20.00
    £400.00-£800.00 £ 30.00
    £800.00 and Over £ 60.00

    I’ve written the following, but it’s not working. Any glaring errors that you can see?

    $threshold1 = 200;
    $threshold2 = 400;
    $threshold3 = 800;

    if ( WC()->cart->subtotal cart->subtotal cart->subtotal < $threshold3 ) {

    unset( $rates['flat_rate:1'], $rates['flat_rate:2'], $rates['flat_rate:7'], );

    }

    return $rates;

    Thank you,

    Jamie

  15. Hi there,

    This code looks great to use but I was wondering what to do if I only have one flat rate meaning orders up until 250 euros require shipping costs and above it’s free.

    Thanks!

    1. Hey Delana, thanks for your comment 🙂 You need no snippet for that! Just create 2 shipping methods, and set up the Free Shipping with a minimum threshold 🙂

  16. I have 5 rates, ranging from $0 to > $100, do I repeat this block 5 times and just change the amount?
    [php]
    $threshold = 100;

    if ( WC()->cart->subtotal < $threshold ) {

    if ( isset( $rates['flat_rate:1'] ) ) unset( $rates['flat_rate:2'] );

    } else {

    if ( isset( $rates['flat_rate:2'] ) ) unset( $rates['flat_rate:1'] );

    }

    return $rates;
    {/php]

    1. Hey Liz! No, you’d do something like this:

      $threshold1 = 20;
      $threshold2 = 40;
      // etc..
      
      if ( WC()->cart->subtotal < $threshold1 ) {
      
      unset( $rates['flat_rate:2'], $rates['flat_rate:3'], $rates['flat_rate:4'], etc... );
      
      } elseif ( WC()->cart->subtotal < $threshold2 ) { 
      
      unset( $rates['flat_rate:1'], $rates['flat_rate:3'], $rates['flat_rate:4'], etc... );
      
      } elseif ( etc... ) { 
      
      }
      
      return $rates;
      
    2. Hi Rodolfo,
      I didn’t see your response here til today when I got back to working on this. Thank you for the model of what to use!!! How do you indicate a threshold for everything above a dollar amount? Threshold 6 is for everything above $100. The other thresholds (have no idea what happened to #2) all calculate properly in the cart but once I have over $100 of products, it displays all of the price points. Site is still in progress ..

      	$threshold1 = 25;
        	$threshold3 = 50;
      	$threshold4 = 70;
      	$threshold5 = 100;
      	$threshold6 = 10000;
      // etc..
       
      if ( WC()->cart->subtotal < $threshold1 ) {
       
      unset( $rates['flat_rate:3'], $rates['flat_rate:4'], $rates['flat_rate:5'], $rates['flat_rate:6'] );
       
      } elseif ( WC()->cart->subtotal < $threshold3 ) { 
       
      unset( $rates['flat_rate:1'], $rates['flat_rate:4'], $rates['flat_rate:5'], $rates['flat_rate:6'] ); 
       
      } elseif ( WC()->cart->subtotal < $threshold4 ) { 
       
      unset( $rates['flat_rate:1'], $rates['flat_rate:3'], $rates['flat_rate:5'], $rates['flat_rate:6'] ); 
       
      } elseif ( WC()->cart->subtotal < $threshold5 ) { 
       
      unset( $rates['flat_rate:1'], $rates['flat_rate:3'], $rates['flat_rate:4'], $rates['flat_rate:6'] ); 
       
      } elseif ( WC()->cart->subtotal < $threshold6 ) { 
       
      unset( $rates['flat_rate:1'], $rates['flat_rate:3'], $rates['flat_rate:4'], $rates['flat_rate:5'] ); 
       
      }
       
      return $rates;
      
    3. Liz, in that code you have a big problem 🙂

      When you check:

      if ( WC()->cart->subtotal < $threshold1 )
      

      this also triggers when:

      WC()->cart->subtotal < $threshold3
      

      So, you have to work with those ranges and make sure every elseif does not override the previous one.

      Hope this helps

  17. This is exactly the solution I was looking for but in my case I have three flat rates (No free shipping):
    Orders up to $50 in value = $10 for shipping
    Orders from $51-$100 in value = $20 for shipping
    Order above $100 in value = $30 for shipping

    Do I have to define three different threshold variables? Any guidance would be greatly appreciated.

    1. Stella, thanks for your comment and corect, you’ll need to define a second threshold and do some more work with the if>else in PHP. Good luck 🙂

    2. Hi there,

      Currently I’ve got 2 Shipping Zones, one Zone for specific Post Code areas and I’ve followed the tutorial and got it working just fine.

      However, in the second Zone for everywhere else I’ve got the same shipping methods setup (but with different prices) but just not sure how to apply them to the snippet?

    3. Darren, thanks for your comment! I’m afraid it’s not 100% clear what you’re trying to achieve. Can you link to a screenshot of your settings and explain your objective again? Thank you 🙂

  18. Precisely what I’ve been looking for for ages, thanks!

    Expanding on this method I was also able to unset other superfluous delivery options that had free versions of themselves over a certain amount.

    1. Brilliant, thanks for your feedback Andrew!

    2. Hi Rodolfo,

      So sorry I missed your reply back in March.

      So basically we’ve got two Shipping Zones – one for Local – Tiered Shipping which we’ve used the code you provided and that works fine – https://pasteboard.co/g432wor4e.png as you can see the shipping option shows based on price (plus a local collection option which is fine).

      We’ve got a second Shipping Zone – Rest of UK – Tiered Pricing and want to use the same code or adapt the code. At the moment this is what the checkout page looks like – https://pasteboard.co/g3WO1BxAF.png

      So you can see the customer select their shipping price, we would like the shipping to be based on their Subtotal.

      So we’d like to be able to adapt the code so that it can be used for both Shipping Zones – Local – Tiered Shipping and Rest of UK – Tiered Pricing.

      Hope that makes sense? Darren

    3. Darren, thanks so much for your comment! Yes, it makes sense and it is possible – but unfortunately this is custom work and I cannot provide a complementary solution here on the blog. Thanks a lot for your understanding! ~R

Questions? Feedback? Support? Leave your Comment Now!
If you're writing code, please wrap it between: [php] code_here [/php]

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.