All Tutorials

Manage fulfillments as a fulfillment service app

All Tutorials

Manage fulfillments as a fulfillment service app

Manage fulfillments as a fulfillment service app

API versions 2020-01 and higher

This guide describes how fulfillment service apps can use the GraphQL Admin API to manage fulfillments in Shopify. If your app manages a FulfillmentService location (such as a third-party warehouse that prepares and ships orders on behalf of the store owner), then follow this guide. If your app doesn't manage a FulfillmentService location, but it assigns orders to be fulfilled by the merchant or by third-party locations, then you can follow our guide on managing fulfillments as an order management 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 a fulfillment service 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 fulfillment service apps

Apps acting as fulfillment services need to request the write_assigned_fulfillment_orders permission using OAuth. 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.

Registration

A fulfillment service needs to opt into fulfillment orders by letting Shopify know that they’re ready to perform FulfillmentOrder-based fulfillment, rather than order-based fulfillment. This has the following consequences:

  • Shopify won't create pending fulfillments for the fulfillment service when fulfillment is requested.
  • Shopify will create successful fulfillments instead of pending fulfillments for the fulfillment service when a fulfilled order is imported.

The communication pattern between Shopify and the app changes, no longer relying on fulfillment create webhooks but rather on explicit POSTs to the callback URL (services won't be able to receive fulfillment and cancellation requests unless they have a callback URL registered). This can be done by using the fulfillmentServiceUpdate mutation.

mutation {
  fulfillmentServiceUpdate(
    id: "gid://shopify/FulfillmentService/5",
    fulfillmentOrdersOptIn: true,
    callbackUrl: "https://www.myapp.com/callback_url",
  ) {
    fulfillmentService {
      fulfillmentOrdersOptIn
  }
}

Before opting into FulfillmentOrder-based fulfillment, your app must meet the following requirements:

  • All assigned fulfillments must be completed (none in PENDING or OPEN states).

You can also opt out FulfillmentOrder-based fulfillment after you’ve opted in. But before you can do so, your app must meet the following requirements:

  • All fulfillment requests must be rejected
  • All cancellation requests must be accepted
  • All in-progress fulfillment orders that were previously accepted must either have their fulfillment orders closed or their fulfillments created

If the assignedFulfillmentOrders query doesn’t return any results, then you should be able to opt out of FulfillmentOrder-based fulfillment.

Receiving FULFILLMENT_REQUEST notifications

To receive notifications for fulfillment requests, an endpoint must be configured on the callback URL where the requests will be sent. This endpoint should be structured in the following format: <callback_url>/fulfillment_order_notification.

The payloads that it will receive include a field titled kind, which can be one of either FULFILLMENT_REQUEST or CANCELLATION_REQUEST. When a merchant requests a fulfillment from their Shopify admin, Shopify sends the fulfillment service a notification with kind set to FULFILLMENT_REQUEST. In this case, the body of the request sent to the callback URL contains {"kind":"FULFILLMENT_REQUEST"}. If the merchant submitted a cancellation request, then the body would include {"kind":"CANCELLATION_REQUEST"} instead.

Acting on FULFILLMENT_REQUEST notifications

A fulfillment order notification of FULFILLMENT_REQUEST indicates that there’s a new fulfillment order assigned to the service, and it’s ready to be acted on (since the merchant has requested a fulfillment). The fulfillment service is now expected to query the assignedFulfillmentOrders connection that’s exposed under the shop object, optionally passing in the argument assignmentStatus set to FULFILLMENT_REQUESTED. The fulfillment service can then specify the fields that it requires to determine whether it wants to accept or reject a given fulfillment request.

In the following example, the query requests the fulfillment order line item and line item SKUs, along with the merchant requests, which include whatever note the merchant supplied when they submitted the request.

{
  shop {
    assignedFulfillmentOrders(first: 10, assignmentStatus: FULFILLMENT_REQUESTED) {
      edges {
        cursor
        node {
          id
          lineItems(first: 10) {
            edges {
              cursor
              node {
                id
                totalQuantity
                lineItem {
                  sku
                }
              }
            }
          }
          merchantRequests(first: 10, kind: FULFILLMENT_REQUEST) {
            edges {
              cursor
              node {
                message
              }
            }
          }
        }
      }
    }
  }
}

The fulfillment service can now review each fulfillment order requested above, and determine whether it can be fulfilled. To accept a given fulfillment order, the fulfillment service must send an accept request by using the fulfillmentOrderAcceptFulfillmentRequest mutation. It can also send an optional message:

mutation {
   fulfillmentOrderAcceptFulfillmentRequest(
     id: "gid://shopify/FulfillmentOrder/5",
     message: "Reminder that tomorrow is a holiday, we won't be able to ship this until Monday"
   ){
    fulfillmentOrder {
      status
      requestStatus
    }
}

After the fulfillment service sends an accept request, the fulfillment order card in the Shopify admin indicates to the merchant that the request was accepted.

Shows a fulfillment card indicating that the request accepted

If the fulfillment service determines that it can’t fulfill the order, then it rejects the fulfillment request by using the fulfillmentOrderRejectFulfillmentRequest mutation:

mutation {
   fulfillmentOrderRejectFulfillmentRequest(
     id: "gid://shopify/FulfillmentOrder/5",
     message: "We are out of inventory for these items"
   ){
    fulfillmentOrder {
      status
      requestStatus
    }
}

In this case, the fulfillment order card in the Shopify admin indicates to the merchant that the request was rejected.

Shows a fulfillment card indicating that the request was rejected

Creating fulfillments

After accepting a fulfillment request, the fulfillment service can begin creating fulfillments. Note that fulfillments can’t be created for a fulfillment order before the fulfillment service accepts the fulfillment request. The fulfillment service can create multiple fulfillments for a given fulfillment order if required, representing multiple packages to be shipped.

When an app accepts a fulfillment request, the fulfillment order status transitions to IN_PROGRESS. After all line items on the fulfillment order are entirely fulfilled, the fulfillment order status transitions to a CLOSED state. If no individual lineItemsByFulfillmentOrder.fulfillmentOrderLineItems are provided, then the app creates a fulfillment for all remaining line items.

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/8",
          quantity: 3,
        }
      ]
    }
  }) {
    fulfillment {
      trackingInfo {
        url
        number
        company
      }
    }
  }
}

Receiving CANCEL_REQUEST notifications

Cancellation requests can also be sent to the notification endpoint URL of <callback_url>/fulfillment_order_notification. The payload of the request includes a kind, with the value CANCELLATION_REQUEST. A merchant can request cancellation through the Shopify admin by using the menu displayed below.

Image of the Request cancellation button

This renders a modal to submit a cancellation request, which lets the merchant add specific notes about this cancellation request for the fulfillment service.

After the modal is submitted, the fulfillment order card indicates that this work is now pending cancellation.

Image of the fulfillment pending cancellation

Acting on CANCEL_REQUEST notifications issued by the merchant

Receiving a fulfillment order notification with a type of CANCELLATION_REQUEST indicates that a merchant has requested for a fulfillment order to be cancelled. Similar to acting on a fulfillment request, a fulfillment service should now query assignedFulfillmentOrders and pass in the argument assignmentStatus, but set it to CANCELLATION_REQUESTED instead.

{
  shop {
    assignedFulfillmentOrders (first: 10, assignmentStatus: CANCELLATION_REQUESTED) {
      edges {
        cursor
        node {
          id
          lineItems(first: 10) {
        edges {
             cursor
             node {
           totalQuantity
               lineItem {
                 sku
               }
         }
          }
          merchantRequests(first: 10, kind: CANCELLATION_REQUEST) {
        edges {
              cursor
              node {
            message
         }
          }
        }
      }
    }
  }
}

A fulfillment service can’t create fulfillments for the fulfillment orders that are returned in this list until it accepts or rejects the cancellation requests. The fulfillment service can now review each fulfillment order, and determine whether they can be cancelled. If it can, then the fulfillment service can submit a request by using the fulfillmentOrderAcceptCancellationRequest mutation to accept the cancellation of the fulfillment order along with an optional message. This results in a fulfillment order where requestStatus is CANCELLATION_ACCEPTED.

mutation {
   fulfillmentOrderAcceptCancellationRequest(
     id: "gid://shopify/FulfillmentOrder/5",
     message: "Item was not picked and packed yet, cancelling as requested"
   ){
    fulfillmentOrder {
      status
      requestStatus
    }
}

After the fulfillment service accepts a cancellation request, the fulfillment order card in the Shopify admin indicates to the merchant that the cancellation request was accepted.

Image of the accepted fulfillment cancellation request

If the fulfillment service determines that it can’t accept the cancellation of the fulfillment order, then it rejects the cancellation request by using the fulfillmentOrderRejectCancellationRequest mutation. This results in a fulfillment order where requestStatus is CANCELLATION_REJECTED. The fulfillment service can continue to create fulfillments after rejecting a cancellation.

mutation {
   fulfillmentOrderRejectCancellationRequest(
     id: "gid://shopify/FulfillmentOrder/5",
     message: "This was already picked up by the courier"
   ){
    fulfillmentOrder {
      status
      requestStatus
    }
}

In this case, the fulfillment order card in the Shopify admin indicates to the merchant that the cancellation request was rejected.

Image of the rejected fulfillment cancellation request

Merchants forcefully cancelling fulfillment orders

Merchants can cancel a fulfillment order before the fulfillment service responds to the cancellation request. This option is provided immediately after requesting cancellation. A warning message is also displayed to the merchant, indicating that forcibly cancelling a fulfillment order doesn’t guarantee that the fulfillment service will not ship the items. Once the fulfillment service responds by either rejecting or accepting the cancellation request, the option is no longer provided to the merchant. The fulfillment service can’t create fulfillments against a fulfillment order that was cancelled by the merchant.

Fulfillment services closing fulfillment orders

In some cases, a fulfillment service realizes that it can’t fulfill the requested items only after it’s already accepted a fulfillment order request. The fulfillment service can then make a call to close the fulfillment order, indicating to the merchant that they will not be performing fulfillment. This results in a fulfillment order where the status is INCOMPLETE and the requestStatus is CLOSED.

mutation {
   fulfillmentOrderClose(
     id: "gid://shopify/FulfillmentOrder/5",
     message: "Apologies but it appears we are out of stock."
   ){
    fulfillmentOrder {
      status
      requestStatus
    }
}

After a fulfillment service cancels a fulfillment, the fulfillment order card in the Shopify admin notifies the merchant that the fulfillment request has been canceled.

Image of a cancelled fulfillment request

Cancelling fulfillments

Fulfillments can be canceled through the API by using the fulfillmentCancel mutation. However, any fulfillment orders that the fulfillment was created against will be affected:

  • If the underlying fulfillment order was entirely fulfilled, then it will have been automatically CLOSED. Upon cancellation of an associated fulfillment, a new fulfillment order is created consisting of the line items from the canceled fulfillment. The new fulfillment order is assigned to the same location as the original fulfillment order if the items are still stocked there. Otherwise, the new fulfillment order is assigned to a location where the items are stocked, considering the shop’s fulfillment priority settings. This may result in multiple newly opened fulfillment orders if all items from the cancelled fulfillment cannot be sourced from the same location.
  • If the fulfillment order was partially fulfilled, then the fulfillmentOrderLineItem remainingQuantity is adjusted back based on the canceled fulfillment’s line items.
mutation {
   fulfillmentCancel(id: "gid://shopify/Fulfillment/2"){
    fulfillment {
      status
    }
  }
}

Managing your inventory

To learn more about managing inventory through the API, see the Updating inventory guide.

API references