All Tutorials

Manage fulfillments as an order management app

All Tutorials

Manage fulfillments as an order management app

Manage fulfillments as an order management app

API versions 2020-01 and higher

This guide describes how order management apps can use the GraphQL Admin API to manage fulfillments in Shopify. If your app assigns orders to be fulfilled by the merchant or by third-party locations, then follow this guide. If your app uses its own location (such as a third-party warehouse that prepares and ships orders on behalf of the store owner), then follow our guide on managing fulfillments as a fulfillment service app.

Using the FulfillmentOrder resource instead of using the Fulfillment and Order resources separately gives you more control over the ways that you can manage fulfillments. It also provides the following benefits:

  • You can fetch the assigned location of a given group of unfulfilled line items to determine where fulfillment should occur.
  • You no longer need to match SKUs or filter out the items on an order that don’t apply to you before you can determine which items you need to fulfill.
  • Merchants and apps can add notes to requests to improve communication throughout the fulfillment process.
  • The process of making fulfillment and cancellation requests is formalized.

This walkthrough shows a few different ways that an order management app can interact with the GraphQL Admin API.

Before you get started with fulfillment orders, it’s a good idea to make sure that you’re familiar with the following concepts and resources.

The fulfillment lifecycle

When an order is placed in Shopify, the line items are divided into groups of line items based on their location. Each group is referred to as a fulfillment order. These fulfillment orders are then assigned to a location based on the fulfillment priority. A location in Shopify can refer to either a merchant-managed location (a location created in the Shopify admin) or a third-party fulfillment location (a location that’s created when a fulfillment service is registered on that shop).

These fulfillment orders can now be acted on by the merchant, allowing them to request or cancel fulfillment. After a merchant has requested fulfillment, apps can either accept or reject these fulfillment orders, and then create fulfillments and provide tracking information for them.

Fulfillment order

Represents a group of line items to be fulfilled from the same location for the same order.

For example, a merchant receives an order for 2 hats and 2 shirts. The hats are assigned to location A, and the shirts are to be fulfilled by a fulfillment service. In this case the order includes two fulfillment orders:

  • one represents the hats to be fulfilled from location A
  • one represents the shirts to be fulfilled by the fulfillment service

Fulfillment orders are represented in the GraphQL Admin API by the FulfillmentOrder object.

Fulfillment

Represents a shipment of one or more items in an order. It includes the line item that the fulfillment applies to, its tracking information, and the location where fulfillment occurred.

Fulfillments are represented in the GraphQL Admin API by the Fulfillment object.

Fulfillment service

Represents a third-party warehousing, print on demand, or fulfillment service that prepares and ships orders on behalf of the store owner. In Shopify, each fulfillment service is associated with its own location. When you create a fulfillment service, a new location is automatically created and associated with it.

Fulfillment services are represented in the GraphQL Admin API by the FulfillmentService object.

FulfillmentOrderMerchantRequest

A GraphQL object that represents a specific request sent from the merchant to a fulfillment service.

For example, a merchant submits a request for fulfillment of a given fulfillment order to a fulfillment service. The request data, including any notes that the merchant provided, is made available in the API through FulfillmentOrderMerchantRequest, which is a node that’s associated with the FulfillmentOrder.

FulfillmentService app capabilities

Apps that are registered as fulfillment services can use fulfillment orders to do the following:

  • Receive fulfillment requests and cancellation requests on their registered callback URL (callback_url/fulfillment_order_notification) rather than relying on webhooks. This is a requirement to receive requests from Shopify. To learn more about how this works, see Receiving FULFILLMENT_REQUEST notifications and Receiving CANCEL_REQUEST notifications.
  • Clearly accept or reject a fulfillment request to indicate to merchants whether the app intends to perform fulfillment.
  • Accept or reject fulfillment cancellation requests sent by merchants.
  • Exchange fulfillment request notes with merchants when accepting, rejecting, or creating fulfillments to facilitate communication.
  • Create multiple fulfillments for a given fulfillment order to represent multiple packages.
  • Perform fulfillment only for work assigned to them
  • Create a fulfillment at any time once the app has accepted a fulfillment request, marking it as fulfilled.
  • Close a fulfillment order after performing only some of the work.

Permissions for order management apps

Apps that fall under this category need to request either (or both) of the following permissions using OAuth:

  • read or write permissions for merchant_managed_fulfillment_orders to manage fulfillment orders assigned to merchant-managed locations
  • read or write permissions for third_party_fulfillment_orders to manage third-party fulfillment orders on behalf of the merchant

For shops that have already installed your app, follow the migration guide for instructions on how to add these permissions without requiring the merchant to reinstall your app.

Fetching the assigned location ID for a fulfillment order

Apps can use the API to determine which fulfillment location Shopify has assigned to an item. After requesting all fulfillment orders for a given order, apps can identify the origin location of the fulfillment order.

{
  order(id: "gid://shopify/Order/1205103689784") {
    fulfillmentOrders(first: 10) {
      edges {
        node {
          id
          status
          assignedLocation {
            name
            location {
              id
            }
          }
          lineItems(first: 10) {
            edges {
              node {
                id
                totalQuantity
                remainingQuantity
                lineItem {
                  sku
                  variant {
                    id
                    displayName
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

In the above query, the assignedLocation type is a copy of the location record at the time the merchant requested fulfillment of the fulfillment order. This means you must use assignedLocation.location.id to determine the ID of the backing location in Shopify.

Managing orders by using fulfillment order actions

The app needs to request some details regarding the status of the fulfillmentOrder object and the supported fulfillment order actions to determine which actions it can take.

{
  order(id: "gid://shopify/Order/1212222734358") {
    fulfillmentOrders(first: 10) {
      edges {
        node {
          id
          status
          supportedActions {
            action
            externalUrl
          }
          destination {
            address1
            address2
            city
            company
            countryCode
            province
            zip
            firstName
            lastName
            email
            phone
          }
          lineItems(first: 10) {
            edges {
              node {
                id
                remainingQuantity
                lineItem {
                  sku
                }
              }
            }
          }
        }
      }
    }
  }
}

The client should then filter out CLOSED fulfillment orders and inspect the list of supported actions to see what is available to them. The list of supported fulfillment order actions includes:

Each action is used at a different stage in the fulfillment process.

CREATE_FULFILLMENT

For creating fulfillments, the app should also request any information that it needs to determine how to fulfill a given fulfillmentOrder. Typically this includes the destination for this fulfillmentOrder (such as the address to ship the items to), the fulfillmentOrderLineItem’s ID, the remainingQuantity (which is required when submitting a request to create a fulfillment), and the order line item’s SKU.

Note that fulfillmentOrderLineItem.remainingQuantity is the most reliable way to determine the remaining quantity to be fulfilled for a line item that’s included in a given fulfillmentOrder. To find the fulfillable quantity for a given line item for the entire order (not for a specific fulfillmentOrder), use lineItem.fulfillableQuantity instead.

For fulfillment orders that return CREATE_FULFILLMENT as a supported action, it's recommended that you use the fulfillmentCreateV2 mutation. It’s important to note that if the fulfillmentOrder in question is assigned to a third party, then you need to have the third_party_fulfillment_orders permission to perform this action. For fulfillmentOrders assigned to a merchant-managed location, you need the merchant_managed_fulfillment_orders permission.

In the following example, it’s assumed that fulfillmentOrder ID 5 has 2 line items, each with a remaining quantity of 3. The request fulfills all the line items on the fulfillmentOrder:

mutation {
   fulfillmentCreateV2(fulfillment: {
    notifyCustomer: true
    trackingInfo: {
      number: "123456789",
      company: "USPS",
      url: "http://www.google.com"
    }
    lineItemsByFulfillmentOrder: {
      fulfillmentOrderId: "gid://shopify/FulfillmentOrder/5",
      fulfillmentOrderLineItems: [
        {
          id: "gid://shopify/FulfillmentOrderLineItem/6",
          quantity: 3
        },
        {
          id: "gid://shopify/FulfillmentOrderLineItem/7",
          quantity: 3
        }
      ]
    }
  }) {
    fulfillment {
      trackingInfo {
        url
        number
        company
      }
      createdAt
      id
    }
  }
}

If successful, this request transitions the fulfillmentOrder to a CLOSED status since there is no more remaining work to be done on it. If this was a partial fulfillment (for example, only fulfilling fulfillmentOrderLineItem 6), then the fulfillmentOrder would be in an IN_PROGRESS status.

Note that fulfillmentCreateV2 accepts an array of FulfillmentOrderLineItemsInputs, which lets apps create a single fulfillment that bridges multiple fulfillment orders. To do so, all the fulfillment orders in question have to be on the same order, and assigned to the same location.

MOVE

You can move a fulfillment order between locations if it returns MOVE as a supported action. To move a fulfillment order from one location to another, use the fulfillmentOrderMove mutation. Accessing this mutation requires the merchant_managed_fulfillment_orders permission. You can retrieve a list of locations where a fulfillment order can potentially move to with FulfillmentOrder.locationsForMove. Only fulfillment orders assigned to merchant-managed locations can be moved between locations, and moving a fulfillment order has no impact on the shipping rate that the buyer has already paid.

In the following example, an unfulfilled fulfillmentOrder – with a status of OPEN – has been assigned to location one with two line items:

  • 1x hat
  • 1x pants

To move this fulfillmentOrder to location two, you can make a call to FulfillmentOrderMove:

mutation {
  fulfillmentOrderMove(
    id: "gid://shopify/FulfillmentOrder/1",
    newLocationId: "gid://shopify/Location/2",
  ){
    originalFulfillmentOrder {
      status
    }
    movedFulfillmentOrder {
      status
    }
    remainingFulfillmentOrder {
      status
    }
  }
}

If both items are stocked at Location two, then the following information is returned:

  • originalFulfillmentOrder - This is the original fulfillmentOrder that was requested to be moved. The status of this fulfillmentOrder is now CLOSED.
  • movedFulfillmentOrder - This is a new fulfillmentOrder assigned to location two, consisting of both line items (hat and pants). The status of this fulfillmentOrder is OPEN.
  • remainingFulfillmentOrder - In this case, it is NULL since there are no remaining items at location one that need to be fulfilled.

If only one of the items is stocked at Location two (say, the hat), then the following information is returned:

  • originalFulfillmentOrder - This is the original fulfillmentOrder that was requested to be moved. The status of this fulfillmentOrder is now CLOSED.
  • movedFulfillmentOrder - This is a new fulfillmentOrder assigned to location two, consisting of only the hat. The status of this fulfillmentOrder is OPEN.
  • remainingFulfillmentOrder - This is a new fulfillmentOrder assigned to the original location, consisting of only the pants. The status of this fulfillmentOrder is OPEN.

REQUEST_FULFILLMENT

To perform this action, an app requires write on the third_party_fulfillment_orders permission. For fulfillment orders that return REQUEST_FULFILLMENT as a supported action, it is recommended to call the fulfillmentOrderSubmitFulfillmentRequest mutation. This mutation submits a request to the assigned fulfillment service. Clients can also provide an optional message to facilitate communication with the fulfillment service.

In the following example, it’s assumed that the fulfillmentOrder has two line items, each with a remaining quantity of 2. The request submits the entire fulfillmentOrder to the third party (including all line items):

mutation {
  fulfillmentOrderSubmitFulfillmentRequest(
    id: "gid://shopify/FulfillmentOrder/1",
    message: "Please gift wrap.") {
    originalFulfillmentOrder {
      status
      requestStatus
    }
    submittedFulfillmentOrder {
      status
      requestStatus
    }
    unsubmittedFulfillmentOrder {
      status
      requestStatus
    }
  }
}

The submitted fulfillment order is always returned in the submittedFulfillmentOrder field. In this case, the requestStatus is SUBMITTED, and the status is OPEN.

Based on the same example above, if the app wanted to submit only one of the line items on the fulfillmentOrder for fulfillment, then the request might look like this:

mutation {
  fulfillmentOrderSubmitFulfillmentRequest(
    id: "gid://shopify/FulfillmentOrder/1",
    fulfillmentOrderLineItems: [{id: "gid://shopify/FulfillmentOrderLineItem/1", quantity: 2}],
    message: "Please gift wrap.") {
    originalFulfillmentOrder {
      status
      requestStatus
    }
    submittedFulfillmentOrder {
      status
      requestStatus
    }
    unsubmittedFulfillmentOrder {
      status
      requestStatus
    }
  }
}

Note how the above submits an ID of only 1 for the FulfillmentOrderLineItem. Given that this is a partial request, Shopify closes the original fulfillmentOrder for which a fulfillment request was submitted, and opens two new fulfillment orders:

  • originalFulfillmentOrder - This is the original fulfillmentOrder, which contained both line items. The status is CLOSED, and the requestStatus is UNSUBMITTED.
  • submittedFulfillmentOrder - This is a new fulfillmentOrder, which contains a FulfillmentOrderLineItem ID 1 with a quantity of 2 that is submitted to the fulfillment service. The status is OPEN, and the requestStatus is SUBMITTED.
  • unsubmittedFulfillmentOrder - This is a new fulfillmentOrder, which contains the remaining line item that was not submitted (in this case, FulfillmentOrderLineItem ID 2). The status is OPEN, and the requestStatus is UNSUBMITTED.

REQUEST_CANCELLATION

After a fulfillment request is submitted for a fulfillment order, apps can also submit cancellation requests for it. For fulfillment orders that return REQUEST_CANCELLATION as a supported action, it is recommended to call the fulfillmentOrderSubmitCancellationRequest mutation.

{
  fulfillmentOrderSubmitCancellationRequest(
    id: "gid://shopify/FulfillmentOrder/1",
    message: "The customer changed their mind, please cancel!"
  ){
    fulfillmentOrder {
      status
      requestStatus
    }
  }
}

If the request is successful, then the fulfillmentOrder is updated to have a requestStatus of CANCELLATION_REQUESTED. If you call FulfillmentOrderSubmitCancellationRequest on a fulfillment order assigned to a fulfillment service that is not opted into FulfillmentOrder-based fulfillment, then the request will fail.

CANCEL_FULFILLMENT_ORDER

For fulfillment orders that return CANCEL_FULFILLMENT_ORDER as a supported action, it is recommended to call the fulfillmentOrderCancel mutation. Fulfillment orders that return this supported action have a requestStatus of SUBMITTED or CANCELLATION_REQUESTED.

If a fulfillmentOrder has a requestStatus of SUBMITTED, then a request for fulfillment has been sent to the third-party fulfillment service, but it has not been acknowledged. Calling FulfillmentOrderCancel cancels the fulfillmentOrder immediately since the fulfillment service has not yet accepted the fulfillment request. For example, a merchant submits a fulfillment request for a fulfillmentOrder. They then receive an email from the customer, who wants to confirm the size of an item. To stop the fulfillmentService from accepting the fulfillment order request, the merchant chooses to cancel the fulfillment order request in the app until the customer confirms the size. The app makes a call to FulfillmentOrderCancel:

mutation {
  fulfillmentOrderCancel(
    id: "gid://shopify/FulfillmentOrder/1",
  ){
    fulfillmentOrder {
      status
      requestStatus
    }
    replacementFulfillmentOrder {
      status
      requestStatus
    }
  }
}

If a fulfillmentOrder has a requestStatus of CANCELLATION_REQUESTED, then the cancellation request has been sent to the third-party fulfillment service, but they have not yet accepted or rejected the request. Calling FulfillmentOrderCancel cancels the fulfillment order without waiting for the third-party fulfillment service to respond. For example, a merchant submits a cancellation request for a fulfillmentOrder. The merchant chooses to cancel the fulfillment order in the app, even though the third-party fulfillment service could still complete the work. The app makes a call to FulfillmentOrderCancel as displayed in the example above.

The call returns the following:

  • fulfillmentOrder - This is the original fulfillment order, which now has a CLOSED status.
  • replacementFulfillmentOrder - This is the new fulfillment order, which is assigned to the same fulfillment service. This fulfillment order will be in an OPEN status, with a requestStatus of UNSUBMITTED.

If you call FulfillmentOrderCancel on a fulfillment order assigned to a fulfillment service that is not opted into FulfillmentOrder-based fulfillment, then the request will fail.

Fulfillment outside of Shopify

Fulfillment orders that are assigned to an external fulfillment service return EXTERNAL as a supported action. When dealing with a fulfillment order that's assigned to an external fulfillment service, your app should redirect the merchant to the URL in the externalUrl field to initiate the fulfillment process outside of Shopify.

API references