Navigating Shopify's Multi-Carrier Fulfillment Quirk: A Community Deep Dive
Hey everyone,
As a Shopify migration expert, I spend a lot of time sifting through the community forums, and every now and then, a thread pops up that perfectly illustrates a common, yet often misunderstood, Shopify quirk. Recently, a discussion started by a store owner named ctevan caught my eye, and it’s a fantastic example of a challenge many of you face when trying to manage complex fulfillment scenarios, especially with multiple shipments and carriers.
ctevan was running into a wall trying to update tracking information for orders that involved multiple shipments, each potentially with a different carrier. They were using the Shopify API and noticed some strange behavior.
The Head-Scratcher: Why Won't My Multiple Tracking Numbers Show Up?
ctevan initially shared some code, thinking they needed to loop through items and call fulfillmentCreate for each. This was leading to some frustrating "Invalid id" errors:
Exception: Mutation had errors: "Invalid id: gid://shopify/Order/7570290966813", "Invalid id: gid://shopify/FulfillmentLineItem/15855912747293", "Invalid id: gid://shopify/FulfillmentLineItem/15855912780061"
And here's the call they were trying to make:
{
"fulfillment": {
"trackingInfo": {
// ...
},
"notifyCustomer": false,
"lineItemsByFulfillmentOrder": [
{
"fulfillmentOrderId": "{{fulfillment.order.id}}",
"fulfillmentOrderLineItems": [
{% for item in fulfillment.fulfillmentLineItems %}
{
"id": "{{ item.id }}",
"quantity": {{ item.quantity }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
}]
}
}
}
Later, ctevan clarified their primary issue. They were actually using the fulfillmentTrackingInfoUpdate API call, attempting to push multiple tracking numbers and URLs for different carriers (like USPS and UPS) into a single fulfillment ID. The API call itself would run successfully, but here’s the kicker: only the first tracking link would actually show up in the Shopify admin! Take a look at the input they were sending:
{
"fulfillmentId": "gid://shopify/Fulfillment/6566266765597",
"trackingInfoInput": {
"numbers": [
"1111111111111111111",
"55555555555555"
],
"urls": [
"https://tools.usps.com/go/TrackConfirmAction_input?qtc_tLabels1=<TURL>1111111111111111111",
"https://www.ups.com/track?loc=en_US&requester=ST&trackNums=55555555555555"
]
},
"notifyCustomer": false
}
And here's what it looked like in the Shopify admin, showing only one of the tracking links (the USPS one):
The "Classic Shopify Quirk" Explained
This is where another community member, ShopIntegrations, stepped in with some crucial insights that really hit the nail on the head. They perfectly articulated what many of us have learned through trial and error:
"Yeah, this is a classic Shopify quirk. A single fulfillment in Shopify is designed to represent one shipment, meaning it expects a single carrier. While the API technically lets you pass multiple tracking numbers and URLs, mixing carriers (USPS and UPS) in one fulfillment almost always glitches the admin UI. It usually only renders the first one properly or tries to force the second carrier’s number into the first carrier’s tracking link."
This is such an important point! Even if the API lets you push multiple tracking numbers, the Shopify admin interface (which is what you and your customers see) is fundamentally built around the idea of one fulfillment = one distinct shipment. When you try to force multiple carriers or separate shipments into a single fulfillment record, the UI often gets confused, only displaying the first set of details correctly.
The Solution: Split Your Shipments, Split Your Fulfillments
So, what's the definitive answer if you're shipping items from a single order using different carriers or in separate packages?
ShopIntegrations gave us the golden rule: "If you’re shipping items via different carriers, the correct way is to split them into separate fulfillments."
This means that instead of trying to update a single fulfillment with multiple tracking numbers, you should be creating a new, separate fulfillment for each distinct shipment. This aligns perfectly with how Shopify's backend and frontend are designed to operate.
How to Handle Multiple Shipments Correctly via API:
If you're integrating an ERP or a custom shipping solution, here's a conceptual approach to ensure Shopify handles your multi-shipment orders gracefully:
- Identify Separate Shipments: When an order comes in and you determine that its line items will be shipped in multiple packages (e.g., some via USPS, others via UPS; or some items are backordered and will ship later), you need to treat each package as its own logical shipment.
- Create a Fulfillment Order: Before you can create fulfillments, you'll work with Fulfillment Orders. These represent groups of line items that are ready to be fulfilled.
- Mark Line Items for Each Shipment: For each distinct shipment, identify which specific line items (and their quantities) belong to it. You'll need to use the
fulfillmentOrderLineItemsstructure, referencing the correctidandquantityfor each item that's part of this specific shipment. - Call
fulfillmentCreatefor Each Shipment: Instead of trying to update a single fulfillment, you'll make a separatefulfillmentCreateAPI call for each shipment. Each call should specify only the line items included in that particular package, along with its unique tracking information (carrier, number, URL). - Example Structure for Each Fulfillment: Your API call would look something like the initial structure ctevan shared, but you'd make multiple such calls, each with a subset of line items and its own
trackingInfo:
This ensures that each shipment gets its own fulfillment record in Shopify, with its own tracking number and carrier clearly displayed. Your customers will then see each tracking number individually, which is a much clearer experience.{ "fulfillment": { "trackingInfo": { "company": "USPS", "number": "1111111111111111111", "url": "https://tools.usps.com/go/TrackConfirmAction_input?qtc_tLabels1=1111111111111111111" }, "notifyCustomer": true, "lineItemsByFulfillmentOrder": [ { "fulfillmentOrderId": "gid://shopify/FulfillmentOrder/12345", // Use the actual Fulfillment Order ID "fulfillmentOrderLineItems": [ { "id": "gid://shopify/FulfillmentOrderLineItem/67890", // Specific line item for *this* shipment "quantity": 1 } ] }] } } }
What About Those "Invalid ID" Errors?
The "Invalid id" errors ctevan initially encountered (gid://shopify/Order/..., gid://shopify/FulfillmentLineItem/...) are often a sign that the GIDs (Global IDs) being passed in the API call are either incorrect, malformed, or are being used in the wrong context for the specific mutation. When creating fulfillments, you typically need to reference FulfillmentOrder and FulfillmentOrderLineItem GIDs, not necessarily the original Order or general FulfillmentLineItem GIDs directly in the fulfillmentCreate mutation's lineItemsByFulfillmentOrder block. Double-check that you're fetching and using the correct GIDs for the fulfillment orders and their associated line items that you intend to fulfill.
The Curious Case of the Tag
One last detail ctevan mentioned was a mysterious tag appearing in one of their tracking URLs. ShopIntegrations was quick to point out that this definitely isn't something Shopify would inject. Their advice? "It’s almost certainly coming from whatever ERP, shipping app, or upstream system is generating those URLs before they hit your API call. Check your payload source!"
This is a great reminder to always trace your data back to its origin. If something unexpected appears in your API payload, the culprit is usually the system or script that constructed that payload, not Shopify itself.
Ultimately, the key takeaway here is to respect Shopify's underlying data model. While the API can sometimes be flexible, the admin UI (and by extension, the customer experience) thrives on clarity and a one-to-one relationship between a fulfillment record and a physical shipment. By creating separate fulfillments for distinct shipments, especially those with different carriers, you'll avoid those frustrating display glitches and ensure your customers get accurate tracking information every time. It's a small adjustment in your integration logic that makes a huge difference in operational smoothness!
