Creating a custom Magento shipping method

By Michael Whitby

Magento is a feature-rich ecommerce platform that offers several out-of-the-box shipping methods. But if you're building a large ecommerce site, you may meet a requirement to develop your own custom shipping methods. This is especially true if your retail client requires integration with a specific courier or shipping supplier. In this tutorial we look at how you'd go about doing this.

Explaining the shipping architecture

Before making our own module, we’ll look at how Magento’s existing shipping rate functionality operates. If you are already familiar with this, then you should skip to the next section.

For this walkthrough we’ll use the 'Flatrate' shipping method which is available by default with Magento. This method requires two files (excluding inherited classes and so on), these are:

  • app/code/core/Mage/Shipping/Model/Carrier/Flatrate.php
  • app/code/core/Mage/Shipping/etc/config.xml

Open Flatrate.php and take a look. There are four elements of the class which are essential to the operation of this rate.

app/code/core/Mage/Shipping/Model/Carrier/Flatrate.php

protected $_code = 'flatrate';
protected $_isFixed = true;

public function collectRates() {}
public function getAllowedMethods() {}

The first important element is the $_code property. This is a code which must be unique to your shipping method. It’s important to note this code is specified in other places, so you need to be consistent with its naming (more on this later). The next property, $_isFixed, is telling Magento that this shipping method is a fixed, one-time fee, rather than a recurring payment, which is the alternative option here.

The second file is config.xml. This file is for all Magento shipping methods and, as such, is quite large. However, we're only interested in one part of it:

app/code/core/Mage/Shipping/etc/config.xml

<carriers>
<flatrate>
<active>1</active>
<sallowspecific>0</sallowspecific>
<model>shipping/carrier_flatrate</model>
<name>Fixed</name>
<price>5.00</price>
<title>Flat Rate</title>
<type>I</type>
<specificerrmsg>This shipping method is currently unavailable...(etc)</specificerrmsg>
<handling_type>F</handling_type>
</flatrate>
</carriers>

Notice how the node is called <flatrate> to match the $_code property in the Flatrate.php class we saw earlier. Most of the nodes above are easy to understand, but some will benefit from a little more explanation:

Swallow screenshot for shipping module

The <model> node in config.xml, as shown above, tells Magento which shipping class to use; in our example, that is shipping/carrier_flatrate. Magento converts shipping/carrier_flatrate to Mage_Shipping_Model_Carrier_Flatrate (found in app/code/core/Mage/Shipping/Model/Carrier/Flatrate.php).

The class within Magento responsible for loading the correct rate is Mage_Shipping_Model_Shipping. It is a good idea to take a look at this class and familiarise yourself with how Magento handles this.

We can also take a deeper look at the flatrate classfile. This class extends Mage_Shipping_Model_Carrier_Abstract and implements Mage_Shipping_Model_Carrier_Interface.

In fact, since all shipping methods must do the same, we can work out which methods require implementation, and which are already there.

Generally we'll be interested in implementing two methods:

  • collectRates()
  • getAllowedMethods()

All other implementation is provided in Mage_Shipping_Model_Carrier_Abstract, so these two methods are all we need to create our own shipping model.

Creating your own rate

This example shows how to create a simple shipping rate, which can provide the basis of a more complicated rate should you need to adapt this for your own purposes. The first step is to make our module skeleton by creating a directory structure along these lines: 

app/code/local/Foobar/ app/code/local/Foobar/Shipping/ app/code/local/Foobar/Shipping/etc/ app/code/local/Foobar/Shipping/Model/

Then we need to add a module activation file to app/etc/modules/Foobar_Shipping.xml with the following contents:

app/etc/modules/Foobar_Shipping.xml

<?xml version="1.0"?>
<config>
    <modules>
        <Foobar_Shipping>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
                <Mage_Shipping/>
            </depends>
        </Foobar_Shipping>
    </modules>
</config>

Next we make the config file. Here’s the one I used when creating this example:

app/code/local/Foobar/Shipping/etc/config.xml

<?xml version="1.0" ?>
<config>
    <modules>
        <Foobar_Shipping>
            <version>0.1.0</version>
        </Foobar_Shipping>
    </modules>
    <global>
        <models>
            <foobar_shipping>
                <class>Foobar_Shipping_Model</class>
            </foobar_shipping>
        </models>
    </global>
    <default>
        <carriers>
            <foobar_customrate>
                <active>1</active>
                <model>foobar_shipping/carrier_customrate</model>
                <title>Foobar Shipping</title>
                <name>Default Rate</name>
            </foobar_customrate>
        </carriers>
    </default>
</config>

Finally we add our custom shipping class.

app/code/local/Foobar/Shipping/Model/Carrier/Customrate.php

<?php

class Foobar_Shipping_Model_Carrier_Customrate
    extends Mage_Shipping_Model_Carrier_Abstract
    implements Mage_Shipping_Model_Carrier_Interface
{
    protected $_code = 'foobar_customrate';
    protected $_isFixed = true;

    public function collectRates(Mage_Shipping_Model_Rate_Request $request)
    {

        if (!$this->getConfigFlag('active')) {
            return false;
        }

        $result = Mage::getModel('shipping/rate_result');

        $method = Mage::getModel('shipping/rate_result_method');
        $method->setCarrier('foobar_customrate');
        $method->setCarrierTitle($this->getConfigData('title'));
        $method->setMethod('foobar_customrate');
        $method->setMethodTitle($this->getConfigData('name'));
        $method->setPrice(5);
        $method->setCost(2);

        $result->append($method);

        return $result;
    }

    public function getAllowedMethods()
    {
        return array('foobar_customrate' => $this->getConfigData('name'));
    }
}

After clearing your cache, go ahead and add a product to your cart and go to checkout. You should see our shipping method sitting there with a charge (of 5 units of whatever currency your Magento is set up to use). Congratulations: you have a basic shipping module ready for use!

Behind the scenes

When we hit the checkout Magento uses the collectRates() method in our class. With this method you can offer shipping rates (or not) by storing an object in $result, which is Mage_Shipping_Model_Rate_Result.

Basically, you add instances of Mage_Shipping_Model_Rate_Result_Method ($method in our example) to this object via the append() method.

The rate result object holds all the values such as carrier and method name, as well as the all-important price and cost.

In this way it becomes easy to offer multiple rates via the collectRates() method, simply by creating more rate results and calling append() for each one, in this way:

<?php

$result = Mage::getModel('shipping/rate_result');

$method = Mage::getModel('shipping/rate_result_method');
$method->setCarrier('foobar_customrate');
$method->setCarrierTitle($this->getConfigData('title'));
$method->setMethod('foobar_customrate_one');
$method->setMethodTitle('Rate 1');
$method->setPrice(5);
$method->setCost(2);

$result->append($method);

$method = Mage::getModel('shipping/rate_result_method');
$method->setCarrier('foobar_customrate');
$method->setCarrierTitle($this->getConfigData('title'));
$method->setMethod('foobar_customrate_two');
$method->setMethodTitle('Rate 2');
$method->setPrice(5);
$method->setCost(2);

$result->append($method);

After using the code above, you will see two shipping methods available for use. Methods are grouped by carrier, and the Carrier Title and Method Title are displayed on the frontend.

If your shipping method uses multiple rates then it makes little sense to use the config to specify the names attribute, which is why they are hardcoded here. In this case you are free to remove the <name> node from your config.xml, as it is not used anywhere else.

Shipping methods in Magento

This post outlined the absolute essentials for creating a simple custom shipping method. You can now go ahead and use or adapt this for use in your own applications. As with all aspects of Magento, great flexibility is available once you know how to work with it, and hopefully this post will be useful for anyone dealing with shipping rates.