Creating a shipping method in Magento 2

shipping-method

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 🙂

Let us know what you think!


About Domagoj Potkoc

Backend Developer

Domagoj is Magento Certified Developer who enjoys playing tennis after long hours in front of computer screen.

Read more posts by Domagoj / Visit Domagoj's profile

16 comments

  1. 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.

  2. 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?

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

  4. 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??

  5. 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.

  6. 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
  7. 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;
  8. 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.

  9. 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

  10. 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>.