This blog contains full steps with example code snippets to create a custom shipping carrier on Magento 2 stores. By default, Magento supports a few shipping carriers. However, when you want to integrate with other 3rd parties logistics, you need to create a new shipping carrier.

With this simple guide, you can follow step by step to create a new shipping carrier in Magento 2

Step 1: Create a new module

In this blog, we are going to create a new shipping carrier: Magenest_CustomShipping. First of all, we need at least 2 files registration.php and module.xml.

Registration.php

<?php

use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(
   ComponentRegistrar::MODULE,
   'Magenest_CustomShipping',
   __DIR__
);

Module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
   <module name="Magenest_CustomShipping" >
       <sequence>
           <module name="Magento_Store"/>
           <module name="Magento_Sales"/>
           <module name="Magento_Quote"/>
           <module name="Magento_SalesRule"/>
       </sequence>
   </module>
</config>

Step 2: Create the configuration

File system.xml: The system.xml source code declares custom shipping module options:

  • Enabled
  • Title
  • Method Name
  • Shipping Cost
  • Ship to Applicable Countries
  • Ship to Specific Countries
  • Show Method if Not Applicable
  • Sort Order
<?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="customshipping" translate="label" type="text" sortOrder="900" showInDefault="1" showInWebsite="1" showInStore="1">
               <label>Magenest Custom Shipping Module</label>
               <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1">
                   <label>Enabled</label>
                   <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
               </field>
               <field id="title" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0">
                   <label>Title</label>
               </field>
               <field id="name" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0">
                   <label>Method Name</label>
               </field>
               <field id="shipping_cost" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0" >
                   <label>Shipping Cost</label>
                   <validate>validate-number validate-zero-or-greater</validate>
               </field>
               <field id="sallowspecific" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1">
                   <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="70" 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="80" showInDefault="1" showInWebsite="1" showInStore="0">
                   <label>Show Method if Not Applicable</label>
                   <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                   <frontend_class>shipping-skip-hide</frontend_class>
               </field>
               <field id="sort_order" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0">
                   <label>Sort Order</label>
               </field>
           </group>
       </section>
   </system>
</config>

File config.xml: File specifies default values for custom shipping module options and the shipping module model.

<?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>
           <customshipping>
               <active>0</active>
               <title>Magenest Shipping Title</title>
               <name>Magenest Shipping Method Name</name>
               <shipping_cost>10</shipping_cost>
               <sallowspecific>0</sallowspecific>
               <sort_order>15</sort_order>
               <model>Magenest\CustomShipping\Model\Carrier\Customshipping</model>
           </customshipping>
       </carriers>
   </default>
</config>

Step 3: Create the carrier model

app/code/Magenest/CustomShipping/Model/Carrier/Customshipping.php

<?php

namespace Magenest\CustomShipping\Model\Carrier;

use Magento\Quote\Model\Quote\Address\RateRequest;
use Magento\Shipping\Model\Carrier\AbstractCarrier;
use Magento\Shipping\Model\Carrier\CarrierInterface;

/**
* Custom shipping model
*/
class Customshipping extends AbstractCarrier implements CarrierInterface
{
   /**
    * @var string
    */
   protected $_code = 'customshipping';

   /**
    * @var bool
    */
   protected $_isFixed = true;

   /**
    * @var \Magento\Shipping\Model\Rate\ResultFactory
    */
   private $rateResultFactory;

   /**
    * @var \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory
    */
   private $rateMethodFactory;

   /**
    * @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 = []
   ) {
       parent::__construct($scopeConfig, $rateErrorFactory, $logger, $data);

       $this->rateResultFactory = $rateResultFactory;
       $this->rateMethodFactory = $rateMethodFactory;
   }

   /**
    * Custom Shipping Rates Collector
    *
    * @param RateRequest $request
    * @return \Magento\Shipping\Model\Rate\Result|bool
    */
   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($this->_code);
       $method->setCarrierTitle($this->getConfigData('title'));

       $method->setMethod($this->_code);
       $method->setMethodTitle($this->getConfigData('name'));

       $shippingCost = (float)$this->getConfigData('shipping_cost');

       $method->setPrice($shippingCost);
       $method->setCost($shippingCost);

       $result->append($method);

       return $result;
   }

   /**
    * @return array
    */
   public function getAllowedMethods()
   {
       return [$this->_code => $this->getConfigData('name')];
   }
}

Step 4: Enable the module

Run this command to activate the new module: 

Php bin/magento setup:upgrade

The new shipping carrier will appear on the checkout page: 

enable module in checkout page magento 2

READ MORE. How to set up free shipping in Magento - Magenest Blog

With these 4 steps, we hope you can create a new shipping carrier in Magento 2 successfully. Thank you for reading!