Create a Custom Shipping Method for WooCommerce

If you have ever developed something for WordPress, be it a theme or a simple plugin, you are already familiar with the modularity of WordPress. WooCommerce is developed with extendability in mind also.

In this tutorial we will create a simple shipping method for WooCommerce which will calculate the cost of shipping. We will also add some restrictions to our plugin so that our shipping method is available only for certain countries.

To follow this tutorial you will need:

  • WordPress
  • WooCommerce plugin installed and activated
  • Editor of choice

Shipping Rules for Our Shipping Method

Before we begin, we will need to define how our shipping method will calculate the cost and where it can ship. 

The cost will be determined by the weight we need to ship and by the zone where we need to ship. A zone is a number which is assigned to countries and to prices. The higher the number, the longer the shipping distance. Our fictional shipping company is shipping only to a few countries:

  • USA
  • Canada
  • Germany
  • United Kingdom
  • Italy
  • Spain
  • Croatia

Our shipping company will be from Croatia, so Croatia is in zone 0. Let's define zones for the other countries:

  • USA: 3
  • Canada: 3
  • Germany: 1
  • United Kingdom: 2
  • Italy: 1
  • Spain: 2

Now that we have all the countries available for shipping with the corresponding zone, it's time to define prices.

  • Zone 0: $10
  • Zone 1: $30
  • Zone 2: $50
  • Zone 3: $70

I have mentioned also the weight, right? Our shipping company can ship up to 100 kg, and the prices are:

  • 0–10 kg: $0
  • 11–30 kg: $5
  • 31–50 kg: $10
  • 51–100 kg: $20

We have everything that we need to create our shipping method. Let's learn a little about the WooCommerce Shipping API.

Introduction to the WooCommerce Shipping Method API

When creating a shipping method, we need to extend the class from the WooCommerce abstract class WC_Shipping_Method. Defined attributes in this class are:

  • $id: ID (slug, keyword) of our shipping. Required.
  • $number: Integer ID.
  • $method_title: Name of our shipping shown in admin.
  • $method_description: Short description of our shipping shown in admin (Optional).
  • $enabled: String Boolean ("yes" or "no") that gives the information if our shipping is enabled and can be used or not.
  • $title: Used to display our shipping name on our site.
  • $availability: Defines if the shipping is available or not.
  • $countries: Array of countries this method is enabled for. Default value is an empty array.
  • $tax_status: Default value is taxable. If it is taxable then the tax will be charged.
  • $fee: Default value is 0. Fees for the method.
  • $minimum_fee: The minimum fee for the method. Default value is null.
  • $has_settings: Defines if this method has any settings. Default value is true.
  • $supports: Array containing features this method supports. Default value is an empty array.
  • $rates: Array of rates. This must be populated to register shipping costs. Default value is an empty array.

Defined methods in the WC_Shipping_Method are:

  • is_taxable(): Returns whether or not we need to calculate tax on top of the shipping rate.
  • add_rate( $args = array() ): Pushes a shipping rate defined in the parameter $args into the attribute $rates.
  • has_settings(): Returns the value of the attribute $has_settings.
  • is_available(): Returns if the shipping is available. If there are countries set in the attribute $countries and the attribute $availability is set to values including, specific or excluding, it will return true or false if the country is available for shipping.
  • get_title(): Returns the title of this shipping.
  • get_fee( $fee, $total ): Returns the fee value for this shipping based on the parsed $fee and $total.
  • supports( $feature ): Returns if this shipping method is supporting a feature or not.

Since the class WC_Shipping_Method extends the class WC_Settings_API, there are more attributes and methods that will not be explained here for the sake of simplicity.

There are also other methods that need to be defined so that the shipping can get or set settings and also calculate the actual cost of the shipping. These methods are:

  • init(): Creates the form fields and settings (can be named differently, as long as we use the methods inside it and call it in the __constructor method).
  • calculate_shipping( $package ): This is the method used to calculate the cost for this shipping. Package is an array with the products to ship.

In the calculate_shipping method, we are adding the rate with the method add_rate. This method accepts an array with several options:

  • id: ID of the rate.
  • label: Label for the rate.
  • cost: Amount of the shipping. It can be a single value or an array with costs for each item in the cart.
  • taxes : It accepts an array of taxes or nothing so the tax is calculated by WooCommerce. It can even accept false if you do not want the tax to be calculated.
  • calc_tax: Accepts per_order or per_item. If you use per_item, an array of costs will need to be provided.

To register the shipping method, we need to add our shipping method in the array of the registered method by passing the name of our class. We can get access to that array and send the modified array back using a WordPress filter that is defined inside the WooCommerce plugin. That filter is called woocommerce_shipping_methods.

Creating the New Shipping Class

We will create our shipping method as a new plugin that extends the WooCommerce plugin. Create a new folder tutsplus-shipping under wp-content/plugins. Also, create a file with the same name tutsplus-shipping.php and add this code:

It may seem as if there is a lot to understand, but it is quite simple. First we check if the constant WPINC is defined because if not, it means that someone is trying to access this file directly or from a location that is not WordPress.

Now that we are sure that this will be accessed from WordPress, we can move on. Before we start creating our shipping method for WooCommerce, we need to be sure that WooCommerce is active. We are checking if the file woocommerce.php is in the array of active plugins that is saved in the database under the option active_plugins.

We are then creating the function tutsplus_shipping_method which we are also adding in the action woocommerce_shipping_init. The action woocommerce_shipping_init is the main action for WooCommerce Shippings which includes all the shipping classes before they get instantiated. By using that action we are sure that our shipping method will be included after WooCommerce has been initialised and in the right place for WooCommerce to use it.

The __construct method of our class will set some general attributes. Some of them can be easily overwritten after the settings are loaded from the database in the method init. Two other methods are left empty because we will define them later.

Setting Country Availability

Our shipping method is available only in the previously determined list of countries. This will be set before the method init inside the method __construct. Add this in the method __construct:

The attribute availability is set to 'including' so that this shipping is only available for the countries included in the attribute countries. When WooCommerce wants to display the available shippings to our customer, it will check if the shipping country is included in the attribute countries for this shipping.

Creating Settings

If you look closely to our method __construct, you can see that we are checking the settings for the enabled and title properties. We will create fields that will enable our properties to be changed. 

Copy this code and populate our method init_form_fields:

Now you can go to WordPress administration and change those settings at WooCommerce > Settings > Shipping > TutsPlus Shipping. 

The TutsPlus Shipping Options Screen

Try changing the setting Enable and see on the tab Shipping Options if our shipping method is enabled or not.

We have also defined that our shipping method can ship up to 100 kg. What if that rule changes in the near future? We can easily allow that to be edited in the settings. We will add that setting so our method init_form_fields looks like this now:

Now we can even set the maximum weight under the settings. We could make our countries, zones and everything else also customisable, but for the sake of simplicity we will skip that part. 

Calculating the Shipping Cost

With everything set now, we have one more step before we can use our shipping method when selling our products. We need to calculate the shipping cost based on the products in the visitor's cart.

We are going to update the method calculate_shipping one step at a time so that you can understand each step. The first step is to get the cost by weight. Add this code to that method:

We have defined a few starting variables: $weight, $cost, and $country. The variable $weight will hold the total weight from all the products, the variable $cost will hold the cost of this shipping method, and the variable $country will hold the ISO code for the selected shipping country.

We are getting the total weight by iterating the cart and adding the weight for each product in the cart to the variable $weight. Once we have our total weight, we are using the function wc_get_weight to convert the weight into kilograms since that is the unit in which our shipping method has set the limit.

The last thing here is to get the cost for the calculated weight. If you look closely at the last part, we did not set the limit on the 100 kg as we said. Our shipping cost will show the cost even for a cart with a total weight of 101 kg or more. This part of our shipping limits will be done later in the article as a restriction when processing checkout or updating the order.

Calculating Cost for the Selected Shipping Country

Now that we have the cost based on the weight of the cart, we have to calculate the cost for the selected shipping country. Add this code next:

The array $countryZones holds the zones for each country. The second array $zonePrices holds the prices for each zone. Once we have set both of those arrays, we get the cost by zone like this:

  • We pass the ISO country code to the array $countryZones to get the zone.
  • We pass the returned zone to the array $zonePrices to get the cost.
  • We add the returned cost to the variable $cost.

Registering the Rate

We calculated the cost by the total weight, and we have also added the cost by the shipping country. The last step here is to register the rate, so add this last part:

Here is the whole code for this method if you had trouble following it:

If you try now to view your cart or go to the checkout page and select a country that is available for this shipping, you'll get a shipping cost displayed with this custom shipping method. Here is a picture with a country from zone 3:

Cost of our custom shipping method with a shipping country from zone 3

Adding Restrictions

Since we have allowed the shipping method to register its rate even if the total weight of our cart is more than our limit, we have to add some restrictions. Our restriction will notify the customer that the order can't ship because of its weight.

Sorry 120 kg exceeds the maximum weight of 100 kg for TutsPlus Shipping

Restriction Function

In our function we will look for the shipping method which has been chosen. If the method is our TutsPlus_Shipping_Method then we will check for its weight limit and the total weight in the cart. If the weight from the cart exceeds the weight limit, we will notify our customer.

After the filter woocommerce_shipping_methods add this code:

We are getting the packages which are separated by shipping. If all products are using the same shipping, it will return only one package with all the products. After that, we are getting the chosen methods, and then for each package we are checking if that package is shipped by our shipping method.

If the package is to be shipped with our shipping method, we are setting the weight limit from the settings, and after that we are calculating the total weight of our package. If the total weight exceeds our weight limit, it will notify the customer.

Notifying on Order Update

The order update happens each time the customer changes something on the checkout page. We will add the function tutsplus_validate_order to the action woocommerce_review_order_before_cart_contents. This action is called after everything has been set so that we can get the chosen shipping methods from session and the packages from shipping. Once the customer changes something, this action will trigger our function and add the notice if necessary.

Add this code after our function tutsplus_validate_order:

Notifying on Checkout

When the customer clicks on the button to place the order or buy it, WooCommerce will process all the billing and shipping data alongside the cart contents and the chosen shipping. 

If there are any WooCommerce error notices, it will stop the checkout process and display all those error notices to the customer. Since we have created a function that is using the session to get the chosen shipping methods, we have to add this function to an action that will be triggered after the session has been set. The action which will trigger just before WooCommerce checks if there is any error notice is woocommerce_after_checkout_validation

Let's add our function to that action:

Full Code

Here is the full code for this custom shipping method, so if you had any trouble following this tutorial, you can look here:


The WooCommerce Shipping API enables you to create your own shipping method with simple steps. Limitations and availability can be set inside the shipping method when calculating the availability or the cost for the shipping method, but they can also be set outside the shipping method by using WooCommerce actions.

If you're looking for other utilities to help you build out your growing set of tools for WordPress or for code to study and become more well-versed in WordPress, don't forget to see what we have available in Envato Market.

If you have any questions about this or any other shipping method, you can post them in the comments below. You can also follow me on my blog or on twitter @igorbenic.



Related Articles