Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Custom Shipping Carriers in Magento 2: Fundamentals

Custom Shipping Carriers in Magento 2: Fundamentals

~6 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026

Chapters 62-65 built a payment method step by step through a modern, composed Adapter facade. Shipping methods ("carriers" in Magento's own terminology) work technically differently - closer to the old AbstractMethod path from chapter 62, but with their own quirks.

AbstractCarrier and CarrierInterface

Every shipping method inherits from Magento\Shipping\Model\Carrier\AbstractCarrier and implements CarrierInterface - as of Magento 2.4.8 there is no Adapter facade equivalent the way there is for payment methods. The method code still lives in a classic protected property ($_code), not in a constant injected via a di.xml const argument. Two methods are mandatory:

  • collectRates(RateRequest $request) - the actual calculation, returns either a populated Magento\Shipping\Model\Rate\Result object (chapter 68) or false if the method doesn't apply to this request.
  • getAllowedMethods() - an associative array of every method code this carrier can offer, used among other things by the admin config page and comparison tools like table rate, regardless of whether collectRates() actually returns anything for a given request.
app/code/Mironsoft/Loyalty/Model/Carrier/FreeShippingByPoints.php (skeleton)
<?php

declare(strict_types=1);

namespace Mironsoft\Loyalty\Model\Carrier;

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

/**
 * Skeleton only - chapter 67 fills in the business logic. Note the carrier code
 * lives in a plain protected property, not a class constant fed through di.xml
 * like the payment facade in chapter 62: shipping carriers have no Adapter-style
 * facade equivalent, even in Magento 2.4.8 - AbstractCarrier is still the only,
 * property-based way in.
 */
class FreeShippingByPoints extends AbstractCarrier implements CarrierInterface
{
    /**
     * Carrier code, referenced by carriers/mironsoft_loyalty_freeshipping/* config paths.
     */
    protected $_code = 'mironsoft_loyalty_freeshipping';

    /**
     * Calculates shipping rates for the given request. Required by CarrierInterface.
     *
     * @param RateRequest $request Shipping rate request
     * @return Result|bool
     */
    public function collectRates(RateRequest $request)
    {
        // see chapter 67
    }

    /**
     * Returns the codes and titles of all shipping methods this carrier can offer.
     * Required by AbstractCarrier, used e.g. by the admin's "Applicable Countries"
     * comparison tooling.
     *
     * @return string[]
     */
    public function getAllowedMethods(): array
    {
        return ['freeshipping' => $this->getConfigData('name')];
    }
}

Registration via the model field in config.xml

Just like the payment method in chapter 62, no di.xml virtualType decides which class gets instantiated for a carrier code - instead a model configuration value under carriers/<code>/model in etc/config.xml does - the same registration idea, just under a different config root node (carriers instead of payment):

app/code/Mironsoft/Loyalty/etc/config.xml (excerpt, extends chapter 62)
<?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>
            <mironsoft_loyalty_freeshipping>
                <active>0</active>
                <model>Mironsoft\Loyalty\Model\Carrier\FreeShippingByPoints</model>
                <name>Kostenloser Versand durch Punkte</name>
                <title>Mironsoft Loyalty</title>
                <points_cost>200</points_cost>
                <sallowspecific>0</sallowspecific>
                <sort_order>10</sort_order>
            </mironsoft_loyalty_freeshipping>
        </carriers>
    </default>
</config>

Tipp: Magento\Shipping\Model\Config::getActiveCarriers() reads exactly these carriers/*/active and carriers/*/model values and instantiates every active carrier through the object manager - conceptually the same mechanism as Magento\Payment\Helper\Data::getMethodInstance() for payment methods, just without the extra Adapter/ValueHandlerPool detour from chapter 62.

Achtung: sallowspecific and specificcountry aren't cosmetic extra fields - they control whether a shipping method may ship to specific countries at all, and are evaluated by AbstractCarrier::checkAvailableShipCountries(). If they're missing from system.xml (chapter 67), the country restriction simply can't be configured in the admin, even if the PHP code later evaluates it correctly.

With fundamentals and registration settled, chapter 67 builds the actual business logic: free shipping, unlocked by a sufficient loyalty points balance.