The partial shipment workflow in Magento 2 in detail
As soon as an order is fulfilled from multiple sources or an item becomes available only later, multiple shipments end up attached to a single order. Magento models that cleanly on a technical level, but the pitfalls hide in the details: frontend display, partial refunds, and correctly mapping quantities to shipments. This article walks through the full workflow from the order item level down to the customer-facing view.
Table of Contents
- 1. The data model behind multiple shipments
- 2. Shipping from different sources within one order
- 3. The shipment lifecycle and order status
- 4. Frontend display of multiple shipments
- 5. Edge case: partial refund on a partial shipment
- 6. Interaction with partial invoicing
- 7. Performance for orders with a very high shipment count
- 8. Admin UI and permissions for manual partial shipping
- 9. Common pitfalls in the partial shipment workflow
- 10. Summary
- 11. FAQ
1. The data model behind multiple shipments
An order in Magento consists of an order object with several order items, each order item carries an ordered quantity, an already shipped quantity, and an already invoiced quantity as separate counters. A shipment in turn references the order and holds its own list of shipment items, each pointing to its corresponding order item along with the quantity shipped in exactly that shipment. This separation lets the same order item line be worked off incrementally across multiple shipments, without ever duplicating the original order line.
The critical consistency rule is that the sum of all shipment quantities across all shipments of an order item can never exceed the originally ordered quantity. Magento checks this server side when a new shipment is created, but custom extensions that create shipments programmatically need to replicate this check themselves if they bypass the standard ShipOrderInterface API.
<?php
declare(strict_types=1);
namespace Mironsoft\PartialShipment\Model;
use Magento\Sales\Api\ShipOrderInterface;
use Magento\Sales\Api\Data\ShipmentItemCreationInterfaceFactory;
use Magento\Sales\Api\OrderRepositoryInterface;
/**
* Creates a partial shipment for a subset of order item quantities.
*/
class CreatePartialShipment
{
/**
* @param OrderRepositoryInterface $orderRepository
* @param ShipOrderInterface $shipOrder
* @param ShipmentItemCreationInterfaceFactory $itemCreationFactory
*/
public function __construct(
private readonly OrderRepositoryInterface $orderRepository,
private readonly ShipOrderInterface $shipOrder,
private readonly ShipmentItemCreationInterfaceFactory $itemCreationFactory
) {
}
/**
* Ships the given quantities per order item id for a single source.
*
* @param int $orderId
* @param array<int, float> $qtiesByOrderItemId
* @param string $sourceCode
* @return int Created shipment id
*/
public function execute(int $orderId, array $qtiesByOrderItemId, string $sourceCode): int
{
$items = [];
foreach ($qtiesByOrderItemId as $orderItemId => $qty) {
$items[] = $this->itemCreationFactory->create()
->setOrderItemId($orderItemId)
->setQty($qty);
}
return $this->shipOrder->execute($orderId, $items, false, false, null, [], [], [
'source_code' => $sourceCode,
]);
}
}
2. Shipping from different sources within one order
In an MSI setup with multiple sources, the same order item can be split across several sources because the chosen source selection algorithm could not cover the requested quantity from a single source. For shipping this means a separate shipment has to be created per source, since a single shipment in Magento is always tied to exactly one source. Trying to pack quantities from two sources into one shipment either fails on the source assignment or wrongly attributes the whole shipment to a single source.
In practice, your own fulfillment logic, for example a pick list export for the warehouse, needs to consistently operate per source-shipment pair rather than per order. A warehouse management system that only knows an order as a whole, without distinguishing between the individual source shipments, quickly leads to duplicate picking or to forgotten partial quantities that should have come from a second source.
3. The shipment lifecycle and order status
After every new shipment, Magento automatically updates the order status based on the ratio between ordered and shipped quantity across all order items. Once every item is fully shipped, the order moves to Complete, if only part is shipped it stays on Processing with the additional Partially Shipped indicator in the admin. That intermediate state is not a separate status explicitly defined in the order_status table, it is a derived display calculated from the ratio of quantities.
For custom status extensions or notification logic it matters that this intermediate state cannot be reliably queried through a simple status field, it has to be determined by comparing order item quantities. A custom event observer on sales_order_shipment_save_after works well for checking, after every new shipment, whether the order is now fully shipped or still partially shipped, and for triggering the corresponding customer notifications.
4. Frontend display of multiple shipments
In the customer account, Magento by default shows a dedicated shipment overview per order, listing each shipment separately with its tracking number and included items. That view already works correctly in the default Luma theme, but in a Hyva theme the corresponding template needs to be deliberately rebuilt, since Hyva ships intentionally trimmed-down order view templates and the full shipment list is not always carried over one to one depending on the theme version.
For a good customer experience, every shipment should be clearly displayed with a tracking link, its included items, and the shipping date, instead of showing only a single, merged tracking number for the entire order. Customers who receive a partial delivery otherwise regularly wonder where the rest of their order is, even though it is technically already pending in the system as a separate, not-yet-shipped shipment.
<div class="space-y-6" x-data="{ shipments: window.orderShipments }">
<template x-for="shipment in shipments" :key="shipment.id">
<div class="border rounded-lg p-4">
<div class="flex items-center justify-between mb-2">
<p class="font-semibold">Shipment <span x-text="shipment.number"></span></p>
<a class="text-orange-600 text-sm" :href="shipment.trackingUrl" x-show="shipment.trackingUrl">
Track shipment
</a>
</div>
<p class="text-sm text-gray-500 mb-2" x-text="shipment.shippedAt"></p>
<ul class="text-sm divide-y">
<template x-for="item in shipment.items" :key="item.sku">
<li class="py-1 flex justify-between">
<span x-text="item.name"></span>
<span x-text="'Qty: ' + item.qty"></span>
</li>
</template>
</ul>
</div>
</template>
</div>
5. Edge case: partial refund on a partial shipment
The combination of partial shipment and partial refund is particularly error prone. If an item from the second, not-yet-shipped shipment is canceled before it leaves the warehouse, the refund needs to be created as a credit memo without a reference to any existing shipment, since no physical shipping has happened yet. If, on the other hand, an already shipped item is sent back, the credit memo needs to explicitly reference the corresponding shipment, so the returns logic knows which physical shipment is affected.
A common mistake in custom extensions is implementing refund logic uniformly at the order item level, without distinguishing between already-shipped and still-open partial quantities. That leads to a customer receiving a refund with the wrong return instructions for an item that never left the warehouse, or a refund for an already-shipped partial quantity being wrongly treated as a plain cancellation without a returns process.
6. Interaction with partial invoicing
Analogous to shipments, an order can also have multiple invoices, with configuration controlling whether an invoice is created automatically on the first shipment or independently of it. For partial shipments, common practice is creating a matching partial invoice per shipment covering exactly the shipped items and quantities, instead of invoicing the entire order in full on the very first partial shipment.
If the full order amount is invoiced immediately even though only part has shipped, an accounting discrepancy arises between the actually delivered goods value and the invoiced amount, which at the latest surfaces as correction entries once the still-open part is canceled. For B2B customers with their own invoice verification, this discrepancy is particularly critical, since an invoice for undelivered goods is often automatically rejected there.
7. Performance for orders with a very high shipment count
For B2B orders with hundreds of line items shipped over several weeks in many small partial shipments, the sales grid view in the admin can become noticeably slower, since every order needs all its shipments and their items loaded to display progress correctly. A dedicated report that pre-aggregates and caches shipping progress noticeably relieves admin performance in such cases compared to a live calculation on every page view.
The same applies to the storefront view in the customer account: loading all shipments of an order unfiltered along with their items can lead to noticeable load time with a very high number of partial shipments. Paginating the shipment list or lazy-loading older shipments client side keeps the initial load time of the order overview stable even with an unusually high number of partial shipments.
8. Admin UI and permissions for manual partial shipping
In the admin, the shipment creation screen lets warehouse staff freely adjust the quantity per line before a shipment is saved, so a partial shipment is possible manually even without an automated fulfillment integration. For multi-source orders, the screen additionally shows a source selector per line, letting staff decide which source actually ships from, which matters in particular when the automatic source selection needs to be manually overridden for operational reasons.
Since a manually created shipment directly affects stock, invoicing, and customer communication, access to this function should be secured through a dedicated ACL resource instead of relying on the generic sales permission. That allows, for example, restricting manual partial shipment creation to a specific warehouse team, while other roles can only view orders but not ship them.
9. Common pitfalls in the partial shipment workflow
The most common mistake is building custom fulfillment automation at the order level instead of the source-shipment level, which leads to incorrect picking with multi-source shipping. A second mistake is failing to distinguish already-shipped from still-open quantities in refund logic, which leads to incorrect return instructions. Both mistakes rarely surface in a test system, since simple single-source orders are usually tested there, and only show up with real multi-source orders in production.
A third, often overlooked point is missing adaptation of custom Hyva templates for the shipment overview. If the default order overview is carried over unchanged, customers with partial shipments sometimes see only a single, incomplete tracking number, even though multiple shipments are already correctly created in the admin, which generates unnecessary support requests.
| Component | Responsibility | Granularity | Common Pitfall |
|---|---|---|---|
| Order Item | Ordered, shipped, invoiced quantity | Per order line | Sums across all shipments must never exceed the ordered quantity |
| Shipment | Physical shipment with tracking | Per source and partial shipment | A shipment always belongs to exactly one source |
| Invoice | Partial invoice for shipped items | Per invoicing run | Full invoicing on a partial shipment creates an accounting discrepancy |
| Credit Memo | Refund with or without a shipment reference | Per return or cancellation | Missing shipped/open distinction leads to wrong instructions |
| Frontend view | Display of all shipments in the customer account | Per order, multiple shipments | Hyva templates must deliberately reproduce the full shipment list |
Mironsoft
Magento development, module consulting, and system architecture
A Magento project that needs a second opinion or experienced execution?
We build custom Magento modules, advise on architecture decisions, and take on complex implementations, from service contract planning to production-ready deployment.
Architecture Consulting
Have module and system architecture thought through properly before you build.
Custom Module Development
Build custom Magento modules cleanly, following best practices.
Code Review & Audit
Have existing modules reviewed for performance, security, and maintainability.
10. Summary
Partial Shipment Workflow: Key Takeaways
One shipment, one source
Multi-source shipping creates a separate shipment per source within the same order.
Check quantity consistency
The sum of all shipment quantities must never exceed the ordered quantity per order item.
Differentiate refunds
Already-shipped quantities need a shipment reference in the credit memo, open quantities do not.
Adapt Hyva templates
The full shipment list in the customer account must be deliberately rebuilt.