Creating a shipping method in Magento 2

Creating a shipping method in Magento 2

In this post we will demonstrate how to create Magento 2 module with a shipping method. If you are already familiar with Magento 1, all examples will be very clear. Creating shipping method is pretty easy, so let’s go with an example.

First of all, you will need to create a Magento module. You should create directory structure like in a screen-shot below:

Magento 2 module diorectory structure

After creating a module, you should enable module with a shell script:

php -f /bin/magento module:enable Inchoo_Shipping

Also, you can check is module enabled or not with a command (this script will print a list of all enabled modules):

php -f /bin/magento module:status

Let’s start with a class which handles shipping method. First of all, shipping method should be defined in file config.xml, like in a screen-shot below. Without it, it can’t work. The main node in xml is “default” and child of node “carriers” should have the same name as property $_code in shipping class “Inchoo\Shipping\Model\Carrier\Example“.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
<default>
<carriers>
<example>
<active>1</active>
<sallowspecific>0</sallowspecific>
<model>Inchoo\Shipping\Model\Carrier\Example</model>
<name>Inchoo Example Shipping</name>
<price>15.00</price>
<title>Inchoo Example</title>
<type>I</type>
<specificerrmsg>This shipping method is not available. To use this shipping method, please contact us.</specificerrmsg>
</example>
</carriers>
</default>
</config>

In our config.xml you will notice XML node “model” which define php class “Inchoo\Shipping\Model\Carrier\Example“. This model class is charged for shipping method. In this class should be implemented all logic for shipping calculation.

Also every shipping method should have config options in admin. You can add shipping method options through system.xml file. Example is below:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
<system>
<section id="carriers" translate="label" type="text" sortOrder="320" showInDefault="1" showInWebsite="1" showInStore="1">
<group id="example" translate="label" type="text" sortOrder="0" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Inchoo Example</label>
<field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Enabled</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
<field id="name" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Method Name</label>
</field>
<field id="price" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Price</label>
<validate>validate-number validate-zero-or-greater</validate>
</field>
<field id="handling_type" translate="label" type="select" sortOrder="7" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Calculate Handling Fee</label>
<source_model>Magento\Shipping\Model\Source\HandlingType</source_model>
</field>
<field id="handling_fee" translate="label" type="text" sortOrder="8" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Handling Fee</label>
<validate>validate-number validate-zero-or-greater</validate>
</field>
<field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Sort Order</label>
</field>
<field id="title" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Title</label>
</field>
<field id="sallowspecific" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Ship to Applicable Countries</label>
<frontend_class>shipping-applicable-country</frontend_class>
<source_model>Magento\Shipping\Model\Config\Source\Allspecificcountries</source_model>
</field>
<field id="specificcountry" translate="label" type="multiselect" sortOrder="91" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Ship to Specific Countries</label>
<source_model>Magento\Directory\Model\Config\Source\Country</source_model>
<can_be_empty>1</can_be_empty>
</field>
<field id="showmethod" translate="label" type="select" sortOrder="92" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Show Method if Not Applicable</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
<field id="specificerrmsg" translate="label" type="textarea" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Displayed Error Message</label>
</field>
</group>
</section>
</system>
</config>

Shipping class should look like the example below:

<?php
namespace Inchoo\Shipping\Model\Carrier;
 
use Magento\Quote\Model\Quote\Address\RateRequest;
use Magento\Shipping\Model\Rate\Result;
 
class Example extends \Magento\Shipping\Model\Carrier\AbstractCarrier implements
\Magento\Shipping\Model\Carrier\CarrierInterface
{
/**
* @var string
*/
protected $_code = 'example';
 
/**
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
* @param \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory
* @param \Psr\Log\LoggerInterface $logger
* @param \Magento\Shipping\Model\Rate\ResultFactory $rateResultFactory
* @param \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory
* @param array $data
*/
public function __construct(
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
\Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory,
\Psr\Log\LoggerInterface $logger,
\Magento\Shipping\Model\Rate\ResultFactory $rateResultFactory,
\Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory,
array $data = []
) {
$this->_rateResultFactory = $rateResultFactory;
$this->_rateMethodFactory = $rateMethodFactory;
parent::__construct($scopeConfig, $rateErrorFactory, $logger, $data);
}
 
/**
* @return array
*/
public function getAllowedMethods()
{
return ['example' => $this->getConfigData('name')];
}
 
/**
* @param RateRequest $request
* @return bool|Result
*/
public function collectRates(RateRequest $request)
{
if (!$this->getConfigFlag('active')) {
return false;
}
 
/** @var \Magento\Shipping\Model\Rate\Result $result */
$result = $this->_rateResultFactory->create();
 
/** @var \Magento\Quote\Model\Quote\Address\RateResult\Method $method */
$method = $this->_rateMethodFactory->create();
 
$method->setCarrier('example');
$method->setCarrierTitle($this->getConfigData('title'));
 
$method->setMethod('example');
$method->setMethodTitle($this->getConfigData('name'));
 
/*you can fetch shipping price from different sources over some APIs, we used price from config.xml - xml node price*/
$amount = $this->getConfigData('price');
 
$method->setPrice($amount);
$method->setCost($amount);
 
$result->append($method);
 
return $result;
}
}

In order to properly write php class for shipping method, you should respect some Magento 2 rules. Every Magento 2 shipping class should extend “\Magento\Shipping\Model\Carrier\AbstractCarrier” and implement “\Magento\Shipping\Model\Carrier\CarrierInterface“.

In shipping model you need to create at least two php methods: “getAllowedMethods” and “collectRates“. This methods are required by abstract class and interface. Also, you should define property $_code with value. In our case, that is “example“. It’s related to config.xml and node structure.

Php method “collectRates” accepts parameter “$request” which is instance of class “Magento\Quote\Model\Quote\Address\RateRequest“. This class contains all information about items in cart/quote, weight, shipping address and so on. In this method you can implement all logic for shipping calculation. From this method you can call other services for shipping price calculation but it depends about your integration.

You can see more info in screen-shot below.

shipping request object

If you implemented everything as I wrote, you will be able to see shipping method on checkout.

shipping_method-magento2_checkout
This is very simple example I hope that it will be useful for you 🙂

However, if you’re having questions or need help regarding Magento development, we would be happy to help you out by creating a detailed custom report based on our technical audit. Feel free to get in touch!

You made it all the way down here so you must have enjoyed this post! You may also like:

How to programmatically create customers in Magento 2.3.x Deni Pesic
Deni Pesic, | 2

How to programmatically create customers in Magento 2.3.x

Declarative Schema feature in Magento 2 Josip Kovacevic
Josip Kovacevic, | 6

Declarative Schema feature in Magento 2

Reorder input fields on Shipping and Billing step in Magento 2 Mladen Ristic
, | 14

Reorder input fields on Shipping and Billing step in Magento 2

26 comments

  1. I have used your model its working properly thanks i want to hide this model if price amount is more than $100.
    Could you help me.Thanks in Advanced

  2. Can You please describe application and it’s config to debug magento 2 like in the last screenshot?

  3. HI I have created a shipping module, which returns multiple shipping methods, everything working fine but, on checkout page if you select any one of them its selecting all shipping methods, do you know how to solve this one??

  4. Hi,
    how can i get subtotal value for using it in shipping price value? I wanna my shipping price equal to subtotal/100.

  5. Hi Domagoj,

    Thanks for the great article. Inchoo articles have been huge in developing my understanding of Magento. They always have great information, so thank you for putting in the hard work and writing these articles for me and the rest of the community.

    I am having trouble understanding how to get the data in the current cart/order, so I can pass it to my external shipping API. I expect I’m supposed to make the request in collectRates() function. Does the RateRequest param include information such as destination address, cart items, and weight? If no, where can I get that information? Any help would be much appreciated.

  6. Can you please explain how you are able to inject the class `ResultFactory $rateResultFactory` and this assign it to `$this->_rateResultFactory` in the constructor, but you never declare` _rateResultFactory` anywhere in the class? I am trying to use this example to create my own shipping method.

  7. Can you please write a tutorial about how to add dropdown(selection) to the checkout pages shipping methods section in Magento 2. This feature is needed in some countries where customers can receive their orders from postoffices. In Magento 1.x developers changed phtml file to accomplish this, but Magento 2 uses html files. Also Is it possible to change must have filled fields based on the shipping method chosen? It is needed when shop owner want to know the customer address only for certain carriers.

  8. Hello!
    I need to create a shipping method with additionally user parametr. A new text field must be showed to customer under my shipping methid, if it was select. How i can do this?

  9. Hi, thanks for the article. Any chance of doing a ‘collect in store’ shipping method tutorial?

  10. hey i want to get shipping address city state value at checkout page in Magento 2.
    by using $this->_checkoutSession->getQuote()->getShippingAddress()->getPostcode(); i am able to get post code value but if i do
    $this->_checkoutSession->getQuote()->getShippingAddress()->getCity(); it returns value but after complete page load value becomes empty. what is the problem??

  11. Hi,
    I have created the custom shipping module but I have to customize or create template existing shipping method template . For that I have created custom knockout shipping template but it does not displaying on checkout page. Please help me.

  12. This solution is good but with this code you can’t create “Carrier tracking” in “Shipping and Tracking Information” tab, so it better to use:

    class Example extends AbstractCarrierOnline implements CarrierInterface
  13. to Dinesh:
    to display error message you should type return $error; at the and. My code:

                $error = $this->_rateErrorFactory->create();
                $error->setCarrier($this->_code);
                $error->setCarrierTitle($this->getConfigData('title'));
                $error->setErrorMessage($response);
                $result->append($error);
                return $error;
  14. Hi,
    I have followed eveything from this tutorials, I can see shipping method at checkout. But when I try to place an order, it throws an error message saying “Please specify shipping method”. Please help me.

  15. Hi, i have used your module, its working. Now i want to get Region Id and City Id using this method:

    $request->getDestCity;
    $request->getDestRegionCode;

    This method return an empty value. What is the way to get city or region id ?

    thanks.


    1. $request->getDestCity();
      $request->getDestRegionCode();

      not working

  16. I have created a custom shipping method. And that is working fine. Now i want to display error message , if quantity in cart items are greater than my limit. I have got everything done , my check is working fine.

    I am using this code to return error method

    $result = $this->_rateResultFactory->create();
    $method = $this->_rateErrorFactory->create();
    
    			//return $this->_aupostHelper->getServices($result , $request);
    
    			$method->setCarrier($this->_code);
    			$method->setCarrierTitle($this->getConfigData('title'));
    
    			$method->setErrorMessage($this->getErrorMessage());
    			return $result->append($method); 

    But method is not visible in case of error.

    1. HI,
      I have used your module and its working fine , thanks that is a great help.
      Now i want to implement functionality like , if the cart items does not meet my shipping method conditions , then i can display an error message , like other default shipping modules. Where we can set an error message at back end. I can set the error message at back end , but i am trying to display it on front end when items does not meet my conditions. But message is not displaying and even my shipping method is not displaying.

      Thanks

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <s> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.

Tell us about your project

Drop us a line. We'd love to know more about your project.