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:
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.
If you implemented everything as I wrote, you will be able to see shipping method on 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!
26 comments
Hi,
Can we add a tooltip to shipping method option?
Not work for me on Magento 2.2.5 🙁
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
Can You please describe application and it’s config to debug magento 2 like in the last screenshot?
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??
Will this work with Magento ver. 2.2.1
Hi,
how can i get subtotal value for using it in shipping price value? I wanna my shipping price equal to subtotal/100.
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.
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.
hi, no frontend part? can you add it? please. thanks.
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.
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?
Hi, thanks for the article. Any chance of doing a ‘collect in store’ shipping method tutorial?
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??
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.
Hi Domagoj,
Thanks for your great article.
There is a problem when we need to reload shipping when an address information is changed. For make this working, we need to add some files.
Here is a tutorial to add this http://devdocs.magento.com/guides/v2.0/howdoi/checkout/checkout_carrier.html
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:
to Dinesh:
to display error message you should type return $error; at the and. My code:
Hi Nithin,
Kindly check your shipping code. Type to give a single word.
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.
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.
$request->getDestCity();
$request->getDestRegionCode();
not working
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
But method is not visible in case of error.
Thank you to @MagePsyho, your comment is oxygen for me….
Recently we have written on the same topic and the code is on github if anyone need as reference:
https://github.com/MagePsycho/magento2-custom-shipping
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