Shopify API

Unlocking Shopify Fulfillment: A Developer's Guide to Multi-Shipment & Multi-Carrier API Success

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, leading to a classic Shopify developer dilemma.

Shopify admin order details with multiple separate fulfillments
Shopify admin order details with multiple separate fulfillments

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, which incorrectly mixed order and fulfillment line item IDs:

{
  "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 to adjust tracking info for an existing fulfillment, providing multiple tracking numbers and URLs within a single call. The API call itself ran successfully:

{
  "fulfillmentId": "gid://shopify/Fulfillment/6566266765597",

    "trackingInfoInput":  {
      "numbers": [
        
          "1111111111111111111",
        
          "55555555555555"
        
      ],
      "urls": [
        
          "https://tools.usps.com/go/TrackConfirmAction_input?qtc_tLabels1=1111111111111111111",
        
          "https://www.ups.com/track?loc=en_US&requester=ST&trackNums=55555555555555"
        
      ]  
    },
  "notifyCustomer": false
}

But when checking the Shopify admin, only the first tracking link appeared. What gives?

The Core Shopify Fulfillment Principle: One Fulfillment, One Shipment, One Carrier

The crucial insight, as highlighted by ShopIntegrations in the forum, is that Shopify's fulfillment model is fundamentally designed around the concept of a single fulfillment representing a single shipment, typically from a single carrier. While the API might allow you to pass multiple tracking numbers and URLs within a single trackingInfoInput object, the Shopify admin UI and its underlying logic often struggle to display or process this correctly when different carriers are involved. It will usually prioritize and display only the first carrier's information, or attempt to force the second carrier's number into the first carrier's tracking link, leading to confusion for both merchants and customers.

Key Takeaway: If you're shipping items from the same order via different carriers (e.g., USPS and UPS) or in separate packages that will have distinct tracking, the correct approach is to split them into separate fulfillments within Shopify.

Decoding the "Invalid ID" Errors with fulfillmentCreate

Let's revisit ctevan's initial attempt with fulfillmentCreate and the "Invalid id" errors. These errors (gid://shopify/Order/... and gid://shopify/FulfillmentLineItem/...) are a strong indicator of a mismatch in the type of ID expected by the API. Shopify uses Global IDs (GIDs) which are specific to the resource type (e.g., gid://shopify/Order/, gid://shopify/FulfillmentOrder/, gid://shopify/FulfillmentLineItem/, gid://shopify/LineItem/).

  • The fulfillmentOrderId field in the fulfillmentCreate mutation expects a gid://shopify/FulfillmentOrder/ ID, not a gid://shopify/Order/ ID. A single order can have multiple fulfillment orders, each representing a group of items to be fulfilled together.
  • Similarly, fulfillmentOrderLineItems expects gid://shopify/FulfillmentOrderLineItem/ IDs, which are specific to a fulfillment order, not general gid://shopify/FulfillmentLineItem/ IDs (which are part of an already created fulfillment).

This is a common pitfall: understanding the precise hierarchy and ID types within Shopify's GraphQL API is critical. You first retrieve the fulfillment orders associated with an order, then use their specific IDs and their corresponding line item IDs to create new fulfillments.

The Correct Approach for Multiple Shipments and Carriers

1. Identify Fulfillment Orders

Before creating any fulfillments, you need to know which items belong to which fulfillment order. An order can have one or more fulfillment orders. You can query an order to get its associated fulfillment orders:

query {
  order(id: "gid://shopify/Order/YOUR_ORDER_ID") {
    fulfillmentOrders {
      edges {
        node {
          id
          status
          lineItems(first: 10) {
            edges {
              node {
                id
                quantity
                lineItem {
                  id
                  title
                }
              }
            }
          }
        }
      }
    }
  }
}

2. Create Separate Fulfillments for Each Shipment/Carrier

If you have items going out via USPS and other items via UPS, or even the same items but in two separate boxes with two tracking numbers, you must create two distinct fulfillments. Each fulfillmentCreate call should correspond to one shipment and one carrier's tracking information.

For each fulfillment, you'll specify the fulfillmentOrderId and the specific fulfillmentOrderLineItems that are part of that particular shipment. You can then provide a single trackingInfo object with the carrier and tracking details for that specific shipment.

mutation fulfillmentCreate {
  fulfillmentCreateV2(fulfillment: {
    lineItemsByFulfillmentOrder: [
      {
        fulfillmentOrderId: "gid://shopify/FulfillmentOrder/YOUR_FULFILLMENT_ORDER_ID_FOR_THIS_SHIPMENT",
        fulfillmentOrderLineItems: [
          { id: "gid://shopify/FulfillmentOrderLineItem/ITEM_1_ID", quantity: 1 },
          { id: "gid://shopify/FulfillmentOrderLineItem/ITEM_2_ID", quantity: 1 }
        ]
      }
    ],
    trackingInfo: {
      company: "USPS",
      number: "1111111111111111111",
      url: "https://tools.usps.com/go/TrackConfirmAction_input?qtc_tLabels1=1111111111111111111"
    },
    notifyCustomer: true
  }) {
    fulfillment {
      id
      status
      trackingInfo {
        number
        url
        company
      }
    }
    userErrors {
      field
      message
    }
  }
}

Repeat this mutation for the second shipment, using the appropriate fulfillmentOrderId (if different items) or the same fulfillmentOrderId but different fulfillmentOrderLineItems, and crucially, different trackingInfo for the second carrier (e.g., UPS).

3. Updating Existing Tracking Information

If you need to update tracking for an already existing fulfillment, the fulfillmentTrackingInfoUpdate mutation is correct. However, remember the one-fulfillment-one-carrier rule. If you have two tracking numbers for two different carriers, they should belong to two separate fulfillments. You would call fulfillmentTrackingInfoUpdate twice, once for each fulfillment ID, providing the respective single tracking number and URL.

The Mysterious Tag

Regarding the tag appearing in one of ctevan's URLs, ShopIntegrations was spot on: Shopify does not generate this. It's almost certainly coming from an upstream system – perhaps an ERP, a custom shipping integration, or a third-party app that's generating the tracking URLs before they reach your Shopify API call. Always inspect the payload at its source to pinpoint where such anomalies are introduced.

Best Practices for Robust Shopify Integrations

  • Understand Shopify's Data Model: Invest time in understanding the relationships between Orders, Fulfillment Orders, Line Items, and Fulfillments. This is the foundation for successful API interactions.
  • Validate IDs: Always ensure you are using the correct Global ID type (e.g., gid://shopify/FulfillmentOrder/ vs. gid://shopify/Order/) for the specific API field.
  • Test Thoroughly: Use a development store to test all your API calls in various scenarios (single item, multiple items, multiple shipments, different carriers) before deploying to production.
  • Error Handling: Implement robust error handling in your integration to catch and log API errors gracefully.
  • Consider Apps for Complexity: For highly complex fulfillment scenarios, especially those involving multiple warehouses, drop-shipping, or advanced routing, consider leveraging specialized Shopify apps that are built to handle these intricacies, rather than building everything from scratch.

Conclusion

Managing fulfillments with multiple shipments and carriers in Shopify via the API can seem daunting, but it becomes straightforward once you grasp Shopify's core principle: one fulfillment equals one shipment. By correctly identifying fulfillment orders, creating separate fulfillments for distinct shipments, and ensuring you use the right IDs for the right API calls, you can build robust and reliable integrations.

If you're navigating complex Shopify migrations or building intricate integrations, remember that understanding these nuances is key. At Shopping Cart Mover, we specialize in helping businesses like yours streamline their e-commerce operations, ensuring your data and processes are perfectly aligned with Shopify's best practices.

Share:

Use cases

Explore use cases

Agencies, store owners, enterprise — find the migration path that fits.

Explore use cases