There are a lot of shipping extensions out there that can help you perfect the way your store charges customers for shipping. If you plan on entering your prices manually and you think things will get complex, then I definitely suggest Table Rate Shipping. With the new and improved Flat Rate Shipping interface, you may be able to set up shipping with the core settings alone. And if you would rather get prices straight from a shipping provider, then I suggest USPS, UPS, FedEx, and the various other API shipping extensions.
But there will always be times where you just need to make one little customization to the shipping rates being returned. Maybe you need to add $5 if the customer is in Canada, or perhaps you want to subtract $10 if the customer has more than 10 items in the cart. While Table Rate can help you with these situations, I’m going to provide a few short code snippets that can also help you customize WooCommerce Shipping Rates.
Step One: Find the shipping method you need to modify.
First we need to find the name of the shipping rate that we need to change. The following snippet will output all the method’s available on the cart page:
function wc_ninja_find_all_available_shipping_rates( $rates, $package ) {
foreach ( $rates as $id => $rate ) {
echo $id . ' ';
}
return $rates;
}
add_filter( 'woocommerce_package_rates', 'wc_ninja_find_all_available_shipping_rates', 10, 2 );
In my case, the following rates were returned: flat_rate, free_shipping, and local_delivery. Here is a screenshot of what I see:
(Note that I’ve updates the code for this article, but not the above screenshot. The shipping methods need to now be referenced along with their instance ID.)
Step Two: Modify the Method’s Costs
So now we know the name of the method we want to modify. Time to have fun. For our first example let’s just simply set a new cost for flat rate. Using the same filter as before, our code will look like this:
function wc_ninja_change_flat_rates_cost( $rates, $package ) {
// Make sure flat rate is available
if ( isset( $rates['flat_rate:6'] ) ) {
// Set the cost to $100
$rates['flat_rate:6']->cost = 100;
}
return $rates;
}
add_filter( 'woocommerce_package_rates', 'wc_ninja_change_flat_rates_cost', 10, 2 );
And now, flat rate costs $100:
Cost Based on Number of Items in the Cart
We can now change the cost of a shipping method, time to make this function actually useful. The below snippet will subtract $5 from flat rate if there are more than 10 items in the cart.
function wc_ninja_change_flat_rates_cost( $rates, $package ) {
// Make sure flat rate is available
if ( isset( $rates['flat_rate:6'] ) ) {
// Number of products in the cart
$cart_number = WC()->cart->cart_contents_count;
// Check if there are more than 10 products in the cart
if ( $cart_number > 10 ) {
$rates['flat_rate:6']->cost = $rates['flat_rate:6']->cost - 5;
}
}
return $rates;
}
add_filter( 'woocommerce_package_rates', 'wc_ninja_change_flat_rates_cost', 10, 2 );
Cost Based on the Cart’s Subtotal
This snippet will make the shipping method’s cost free if the cart’s subtotal value is greater than or equal to $100.
function wc_ninja_change_flat_rates_cost( $rates, $package ) {
// Make sure flat rate is available
if ( isset( $rates['flat_rate:6'] ) ) {
// Current value of the shopping cart
$cart_subtotal = WC()->cart->subtotal;
// Check if the subtotal is greater than $100
if ( $cart_subtotal >= 100 ) {
// Set the cost to $0
$rates['flat_rate:6']->cost = 0;
}
}
return $rates;
}
add_filter( 'woocommerce_package_rates', 'wc_ninja_change_flat_rates_cost', 10, 2 );
Cost Based on the Customer’s Country
Now, let’s try a snippet where we make flat rate cost $10 extra if the customer is from Canada or the US. For this, we will need to use the $package variable that we are passing into our function.
function wc_ninja_change_flat_rates_cost( $rates, $package ) {
$destination = $package['destination'];
$country = $destination['country'];
// Make sure flat rate is available
if ( isset( $rates['flat_rate:6'] ) ) {
// Check if the customer is in the United States or Canada
if ( $country == 'US' || $country == 'CA' ) {
// Set flat rate to cost $10 more
$rates['flat_rate:6']->cost = $rates['flat_rate:6']->cost + 10;
}
}
return $rates;
}
add_filter( 'woocommerce_package_rates', 'wc_ninja_change_flat_rates_cost', 10, 2 );
Cost Based on the Coupon’s Used
And lastly, this snippet will make flat rate free if a certain coupon has been used.
function wc_ninja_change_flat_rates_cost( $rates, $package ) {
// Coupons currently used in the cart
$coupons_used = $package['applied_coupons'];
// A custom list of coupons to look for
$special_coupons = array( 'fixed_cart', 'free_shipping' );
// Check if one of our special coupons have been used
if ( array_intersect( $special_coupons, $coupons_used ) ) {
$rates['flat_rate:6']->cost = 0;
}
return $rates;
}
add_filter( 'woocommerce_package_rates', 'wc_ninja_change_flat_rates_cost', 10, 2 );
Summary
This should help get you started on putting together your own custom snippet that modifies shipping the way you need to. I know you can do some of the above using the standard shipping methods, but there are times where you can’t. A common use for needing snippets like these is when you are using an API shipping extension like Fedex or USPS. All you need to do is change out $rates['flat_rate']
for say $rates['fedex:FEDEX_GROUND']
or maybe $rates['usps:flat_rate_box_priority']
, and you then have the power to alter rates being returned straight from the Fedex or USPS api.
As always, these snippets should go in your child theme’s functions.php file. If your child theme is a premium theme (meaning it will be receiving updates) then I recommend using this Theme Customizations plugin to store your snippets.
Hi Caleb, I am trying to achieve something similar but I want to be able to customize my shipping rates inside my custom function in function.php. Since I can’t put add_filter( ‘woocommerce_package_rates’) inside my function I was wondering how can I update the rates so that it persists. In my case the change to rate seems to be just local.
add_action( 'woocommerce_review_order_before_order_total', 'my_woocommerce_review_order_before_order_total' );
function my_woocommerce_review_order_before_order_total( ) {
global $woocommerce;
$shipping_method = $_POST['shipping_method'][0];
$packages = WC()->shipping->get_packages();
$rates = $packages[0]['rates'];
$rates[$shipping_method]->cost = 18;
print WC()->shipping->shipping_total; // 0 and not 18
}
Hi Tripty,
I’m not sure what you mean by “In my case the change to rate seems to be just local”.
I do see that you aren’t hooking your function onto the right hook though. You need to use `woocommerce_package_rates` hook, the one your are using will not work for changing the rates.
You absolutely rock my world, Caleb! After weeks and weeks of trying to figure this out myself, I found your post. Thank you so much!
Very nice snippets – thank you very much!
Hi Caleb,
I have a new role setup for customer_wholesale and I’m trying to customize the checkout calculations for this role. Regular customers pay table rate shipping and tax if from WI. However, we want to use a flat rate of 5% shipping for wholesale customers AND wholesale customers should never be charged tax no matter which state they are from. Can your snippet be modified to achieve this? Of course we want the checkout page to reflect only table rate and applicable tax if regular customer and only special shipping for wholesale customer. Thanks in advance for your expertise!
OK, I’ve added a function to correctly calculate the tax and shipping, BUT still displaying both shipping methods to both user roles. Can anyone help me with a function to display only the correct shipping based on user? So…
If wholesale, show only flat rate
else show only table rate
The rates display on both the cart and checkout pages, but I am hoping to accomplish this in the functions file, without touching the template files.
Thanks so much!
Hey Wendy,
You can use the current_user_can() function in a conditional statement to unset flat rate conditionally. Something like this: https://gist.github.com/WPprodigy/c39c3cd4fb8068eab0eb0133459f75c8
Hello Caleb and thank you for your response. Unfortunately, my attempts result in either flat rate only or radio buttons for both flat rate and table rate. I tried to add an elseif statement into the function, but not exactly sure of the syntax for PHP. The two users I am concerned with are customer and customer_wholesale. Can you provide an example that will turn off table rate for wholesale_customer and turn off flat rate for customer? Thank you so much!
Hello Caleb,
I tried again with this function but do not understand why it won’t work. If one logs in and is not a wholesale customer, they will be presented with both options (flat rate and table rate).
https://gist.github.com/zubird/ec490f048f1d20ed94c84789cd7857bf.js“>
Oops! That didn’t work out very well. Let’s try again: https://gist.github.com/zubird/ec490f048f1d20ed94c84789cd7857bf
Sorry about that!
Got it working. Thank you Caleb!
Awesome, nice job 🙂
My snippet only hide flat rate for non wholesale members. Just needed to be duplicated to hide table rate for wholesale members.
Thank you very much, you saved me a lot of time!
Hi Caleb
This is exactly what i am looking for but nothing seems to change when i add the snippets into functions.php in the theme file.
Is it the right code m inserting or anything updated in woocommerce since then.
https://calebburks.com/customize-woocommerce-shipping-rates/#comment-1299
Does this work for Woocommerce 3.0?
You’ll need to add in the instance ID now with WC 3.0+. I updated some of the snipped in the article.
Does this method still work?
I am using the woocommerce_package_rates and I am trying to unset the Free Shipping if an item contains the shipping Class ‘Too Heavy’. The Id for Too Heavy is 92.
When I hook in here, it doesn’t seem to remove Free Shipping anywhere. Not in the Cart, not in the checkout.
I am also using the Stamps.com plugin for my mailing rates.
https://calebburks.com/customize-woocommerce-shipping-rates/#comment-1299
Caleb,
you made my day with this tutorial! I was looking for a way to have shipping cost go up every 4 products added to the cart….I can use your examples above to implement that!
Thanks for posting this! Always a bonus when a few lines of code can replace a premium WooCommerce add-on.
Thank you!
thanks, This is what I’am looking for
I have no words to thank you, because i didn’t know how to use the ‘packages’ variable, i was wasting too much time crating functions to get country, vendor_id, and so on.