Custom Magento 2 Shipping Plugin Development
When integrating with a courier service like SDEK in Magento 2, a common issue arises: the API returns rates only for orders up to 20 kg, but the store needs to deliver oversized items. Standard modules can't flexibly handle such scenarios. A custom plugin allows implementing any rate logic, including zones, days of the week, and multiple warehouses.
What problems we solve
Rate calculation through a third-party API. The carrier's documentation may be incomplete or change. Typical example: the API returns cost only for orders up to 20 kg, but the client wants to deliver 50 kg equipment. We handle errors, timeouts, sudden field changes.
N+1 queries for group delivery. If the cart has 10 items and each is calculated separately – checkout slows down. Solution: aggregate items – group by single address, cache rates for 30 minutes, invalidate by tag mycourier_rates.
Configuration errors. Developers often forget to add config.xml with defaults – config fields return null, shipping method doesn't appear. Or skip system.xml – admin can't configure API key. We ensure all necessary fields are in admin with encryption via Encrypted backend.
Why order a custom Magento 2 shipping plugin?
A ready-made extension for $50–200 rarely covers business specifics. For example, Boxberry with cash on delivery and pickup point selection – that's already 3 modules in one that need to be stitched together. A custom plugin avoids these issues:
| Criteria | Built-in/Ready-made | Custom plugin |
|---|---|---|
| Support for local carriers | Only global (UPS etc.) | Any via API |
| Rate flexibility | Fixed table or weight | Any formula: zones, day of week, product price |
| Integration with WMS/CMS | No | Via REST/SOAP, RabbitMQ queues, file exchange |
| Checkout UI components | No | Pickup point selection, delivery date, calculator |
| Performance | Standard | Caching, aggregation, async requests |
Our custom plugin processes a cart with 20 items 3 times faster than a ready-made extension with per-item API requests.
How we implement courier service integration
Let's take a real case: a cosmetics online store wanted to deliver orders via DPD with cost calculation by weight and dimensions. No standard solutions existed – DPD only provides an API.
Module architecture. Class Vendor\MyCourier\Model\Carrier\MyCourier extends \Magento\Shipping\Model\Carrier\AbstractCarrier (see Magento documentation). In method collectRates we form a request to DPD: pass weight, city, postal code. We use \Magento\Framework\HTTP\Client\Curl – built into Magento 2, no extra dependencies needed.
<?php
namespace Vendor\MyCourier\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;
class MyCourier extends AbstractCarrier implements CarrierInterface
{
protected $_code = 'mycourier';
public function collectRates(RateRequest $request): ?Result
{
if (!$this->getConfigFlag('active')) {
return null;
}
/** @var Result $result */
$result = $this->_rateResultFactory->create();
$rates = $this->fetchRatesFromApi($request);
foreach ($rates as $rateData) {
$method = $this->_rateMethodFactory->create();
$method->setCarrier($this->_code);
$method->setCarrierTitle($this->getConfigData('title'));
$method->setMethod($rateData['code']);
$method->setMethodTitle($rateData['name']);
$method->setPrice($rateData['price']);
$method->setCost($rateData['price']);
$result->append($method);
}
return $result;
}
private function fetchRatesFromApi(RateRequest $request): array
{
$apiKey = $this->getConfigData('api_key');
$fromCity = $this->getConfigData('from_city');
$toCity = $request->getDestCity();
$postcode = $request->getDestPostcode();
$weight = 0;
foreach ($request->getAllItems() as $item) {
if ($item->getParentItem()) {
continue;
}
$weight += $item->getWeight() * $item->getQty();
}
$payload = json_encode([
'from' => $fromCity,
'to_city' => $toCity,
'postcode' => $postcode,
'weight' => max(0.1, $weight),
'currency' => $request->getPackageCurrency()->getCurrencyCode(),
]);
$this->_curl->addHeader('Authorization', 'Bearer ' . $apiKey);
$this->_curl->addHeader('Content-Type', 'application/json');
$this->_curl->setTimeout(10);
try {
$this->_curl->post('https://api.mycourier.ru/v2/rates', $payload);
$body = $this->_curl->getBody();
$status = $this->_curl->getStatus();
} catch (\Exception $e) {
$this->_logger->error('MyCourier API error: ' . $e->getMessage());
return [];
}
if ($status !== 200) {
return [];
}
$data = json_decode($body, true);
return $data['services'] ?? [];
}
public function getAllowedMethods(): array
{
return [$this->_code => $this->getConfigData('title')];
}
}
Configuration. config.xml sets defaults, system.xml provides admin form. API key is encrypted via \Magento\Config\Model\Config\Backend\Encrypted.
Rate caching. We use CacheInterface with tag mycourier_rates. TTL is 30 minutes. This reduces load on the carrier API and speeds up checkout.
Shipment creation handling. After payment (sales_order_invoice_pay event), observer CreateShipment calls carrier API to create an order, gets tracking number, and automatically creates shipment in Magento. This eliminates manual input.
Pickup point UI component. We add a field via checkout_index_index.xml. Component Vendor_MyCourier/js/pvz-selector loads a list of pickup points and saves the selected one to the order address.
What's included in custom shipping plugin development
- Module source code in a private repository.
- Documentation: configuration description, architecture, update instructions.
- Repository access setup.
- Team training: how to change rates, add new shipping methods.
- 6 months warranty support (bug fixes, adaptation to API updates).
- Post-release assistance during production deployment.
Process
- Analysis. Study carrier API, agree on tariff model, data schema (cities, weights, dimensions).
- Design. Create UML class diagram, define events and plugins.
- Implementation. Write carrier, observer, caching mechanism, configuration.
- Testing. Unit tests for PHP (cover key methods), integration tests in Magento environment. Example: verify that API requests are cached and on failure previous rates are returned.
- Deployment. Build module, publish to Composer, set up CI/CD with compatibility check against target Magento version.
Timeline
| Stage | Duration (working days) |
|---|---|
| Basic carrier with API rate calculation | 3–4 |
| Shipment observer + tracking number | 2–3 |
| Pickup point UI component | 2–3 |
| Integration with MSI (multi-source inventory) | 3–5 |
| Testing and deployment | 2–3 |
Total timeline: from 5 to 12 days depending on API complexity and number of features.
Common mistakes when creating a shipping carrier
- Forgetting to add
system.xml— API key field doesn't appear in admin. - Not specifying defaults in
config.xml— shipping method not visible after module activation. - Not caching rates — each checkout request hits API, slowing performance.
- Not handling API errors — checkout crashes with 500 error when carrier is unavailable.
We'll assess your project for free — just email us or message us on Telegram. Our engineers hold Magento 2 Associate Developer certifications and have extensive e-commerce experience. Contact us for a consultation and we'll help integrate any carrier.







