X
    Categories: Magento 2 BlogsMagento Tutorials for Beginners & Experts
Reading Time: 6 minutes

A shipping module in Magento 2 defines the medium of delivery for the purchased product. It determines the shipping charge that the customer pays during checkout. The shipping fee can be fixed at $5 flat rate on any order, or a dynamically calculated cost through a real-time shipping service. This is based on the details of the order and shipment like weight, origin, destination, etc.

Magento 2 already has many built-in shipping methods, but if you need to create a custom shipping method, then this guide is for you. Today, I am going to show you how to create a shipping module in Magento 2.

Let’s start by creating a custom module in Magento 2. If you are not familiar with it, check out this guide on Create Module in Magento 2.

First of all, create a configuration and registration file of the module. Create module.xml in Magento2Root/app/code/Cloudways/Shippingmodule/etc and paste the following code:

<?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="Cloudways_Shippingmodule" setup_version="1.0.1"></module>
	</config>

Create a registration.php in app/code/Cloudways/Shippingmodule with the following code:

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Cloudways_Shippingmodule',
__DIR__
);

Now, create a class which will handle the Shipping Method. Keep in mind that the shipping method should be defined in Cloudways/Shippingmodule/etc/config.xml file. Without this file, shipping method won’t work.

In config.xml file, the parent node is <default>, and its child node is <carriers> which further defines the property with the same name as the $_code in shipping class. It also has a <model> node that defines the shipping class Cloudways\Shippingmodule\Model\Carrier\Shippingmodule.

Create config.xml file and add the following code:

<?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>
				<cloudways_shippingmodule>
					<active>0</active>
					<sallowspecific>0</sallowspecific>
					<price>0</price>
					<model>Cloudways\Shippingmodule\Model\Carrier\Shippingmodule</model>
					<name>Shipping Module</name>
					<title>Cloudways Shipping Module</title>
					<specificerrmsg>This shipping method is not available. To use this shipping method, please contact us.</specificerrmsg>
				</cloudways_shippingmodule>
			</carriers>
		</default>
	</config>

As we all know, every shipping method should have configuration options in the admin panel. Let’s add shipping method options through the system.xml file that can be created in Cloudways\Shippingmodule\etc\adminhtml directory with the following code:

<?xml version="1.0"?>
	<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../Magento/Config/etc/system_file.xsd">
		<system>
			<section id="carriers" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1">
				<group id="cloudways_shippingmodule" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1">
					<label>Cloudways Shipping Module</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="title" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1">
						<label>Title</label>
					</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="4" showInDefault="1" showInWebsite="1" showInStore="0">
						<label>Shipping Cost</label>
						<validate>validate-number validate-zero-or-greater</validate>
					</field>

					<field id="specificerrmsg" translate="label" type="textarea" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="1">
						<label>Displayed Error Message</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="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0">
						<label>Sort Order</label>
					</field>
				</group>
			</section>
		</system>
	</config>

Last but not the least, I have to create a Shipping Class as defined in the <model> node in config.xml file. Create Shippingmodule.php file in Cloudways\Shippingmodule\Model\Carrier directory and add the following code to it:

<?php

namespace Cloudways\Shippingmodule\Model\Carrier;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\DataObject;
use Magento\Shipping\Model\Carrier\AbstractCarrier;
use Magento\Shipping\Model\Carrier\CarrierInterface;
use Magento\Shipping\Model\Config;
use Magento\Shipping\Model\Rate\ResultFactory;
use Magento\Store\Model\ScopeInterface;
use Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory;
use Magento\Quote\Model\Quote\Address\RateResult\Method;
use Magento\Quote\Model\Quote\Address\RateResult\MethodFactory;
use Magento\Quote\Model\Quote\Address\RateRequest;
use Psr\Log\LoggerInterface;

class Shippingmodule extends AbstractCarrier implements CarrierInterface
{
	protected $_code = 'cloudways_shippingmodule';
	protected $_isFixed = true;
	protected $_rateResultFactory;
	protected $_rateMethodFactory;

	public function __construct(
	ScopeConfigInterface $scopeConfig,
	ErrorFactory $rateErrorFactory,
	LoggerInterface $logger,
	ResultFactory $rateResultFactory,
	MethodFactory $rateMethodFactory,
	array $data = []
	)

	{
		$this->_rateResultFactory = $rateResultFactory;
		$this->_rateMethodFactory = $rateMethodFactory;
		parent::__construct($scopeConfig, $rateErrorFactory, $logger, $data);
	}

	public function getAllowedMethods()
	{
		return [$this->getCarrierCode() => __($this->getConfigData('name'))];
	}

	public function collectRates(RateRequest $request)
	{
		if (!$this->isActive())
		{
			return false;
		}

		$result = $this->_rateResultFactory->create();

		$shippingPrice = $this->getConfigData('price');
		
		$method = $this->_rateMethodFactory->create();
		
		$method->setCarrier($this->getCarrierCode());
		$method->setCarrierTitle($this->getConfigData('title'));
		
		$method->setMethod($this->getCarrierCode());
		$method->setMethodTitle($this->getConfigData('name'));
		
		$method->setPrice($shippingPrice);
		$method->setCost($shippingPrice);
		
		$result->append($method);
		return $result;
	}
}

In this class, you can see I have added some Magento 2 rules. Every Magento 2 shipping class extends \Magento\Shipping\Model\Carrier\AbstractCarrier and implements the \Magento\Shipping\Model\Carrier\CarrierInterface class. I have also defined a variable $_code with its value. This value is related to the config.xml file node structure.

Moreover, I have created two functions getAllowedMethods and collectRates. These methods are required by the abstract class and interface.

Function collectRates has a parameter $request which is the instance of RateRequest class. This Magento\Quote\Model\Quote\Address\RateRequest class contains all information about the items in the cart: quote, weight, shipping/billing address, etc. In this method, I have implemented all logic for shipping calculation.

That’s it, you’re all done! Now, to enable this extension, connect to your Magento hosting server via SSH and run the following CLI command in the root directory of your Magento 2 store:

rm -Rf var/di/* var/generation/* var/cache/* var/log/* var/page_cache/* var/session/* var/view_preprocessed/* pub/static/*
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy -f
php bin/magento indexer:reindex
php bin/magento cache:clean
php bin/magento cache:flush

Now let’s check out the result! Log in to your Magento 2 Admin panel, navigate to STORES > Configuration, expand the SALES section from the left panel and click Shipping Methods.

Here, you’ll see that the new shipping Method has been added. Enable it and set the values according to your need.

If you have implemented everything as it is, your Magento 2 checkout page will have a shipping method.

I hope you found this post useful 🙂 If you have any question or need help, I’d be happy to help you out. Feel free to get in touch by commenting in the box below.

Fayyaz Khattak :Fayyaz is a Magento Community Manager at Cloudways - A Managed Magento Hosting Platform. His objective is to learn & share about PHP & Magento Development in Community. Fayyaz is a food lover and enjoys driving. You can email him at m.fayyaz@cloudways.com