An exchange represents the intent of a buyer to swap one or more items from an order for different items from the same merchant or a third-party fulfillment location. Merchants can manage their exchanges in Shopify, and exchanges apps can take actions on behalf of merchants. This guide shows you how to manage exchanges using the GraphQL Admin API. ## Requirements - Your app can make [authenticated requests](/docs/api/admin-graphql#authentication) to the GraphQL Admin API. - Your app has the `read_returns` and `write_returns` [access scopes](/docs/api/usage/access-scopes). Learn how to [configure your access scopes using Shopify CLI](/docs/apps/build/cli-for-apps/app-configuration). - Your store has existing [orders](/docs/api/admin-graphql/latest/objects/Order) that have been fulfilled. You can return only items that have been fulfilled. If you need to make changes to an unfulfilled item, then you can [edit an order](/docs/apps/build/orders-fulfillment/order-management-apps/edit-orders). - You're using API version `2024-07` or higher. - You've met Shopify's [protected customer data requirements](/docs/apps/launch/protected-customer-data). ## Step 1: Query returnable fulfillments Exchanges are applied to a returnable fulfillment. A returnable fulfillment is an order that has been delivered and is eligible to be returned to the merchant or third-party fulfillment service. To retrieve the fulfillment line items that can be returned in an order, provide the ID of the order in the [`returnableFulfillments`](/docs/api/admin-graphql/latest/queries/returnableFulfillments) query. The response returns the fulfillment line item ID, which you'll use as input to [create a return](#step-3-create-a-return-with-exchanges). > Tip: > You can retrieve the ID of an order using the [`orders` query](/docs/api/admin-graphql/latest/queries/orders). ```graphql query returnableFulfillmentsQuery { returnableFulfillments(orderId: "gid://shopify/Order/1", first: 10) { edges { node { id fulfillment { id } # Return the first ten returnable fulfillment line items that belong to the order. returnableFulfillmentLineItems(first: 10) { edges { node { fulfillmentLineItem { id } quantity } } } } } } } ``` ```json { "data": { "returnableFulfillments": { "edges": [ { "node": { "id": "gid://shopify/ReturnableFulfillment/1", "fulfillment": { "id": "gid://shopify/Fulfillment/1" }, "returnableFulfillmentLineItems": { "edges": [ { "node": { "fulfillmentLineItem": { "id": "gid://shopify/FulfillmentLineItem/1" }, "quantity": 1 } } ] } } } ] } } } ``` ## Step 2 (Optional): Calculate return financials with exchanges You can use the [`suggestedFinancialOutcome`](/docs/api/admin-graphql/latest/objects/Return#field-return-suggestedfinancialoutcome) field to calculate recommended financials for a return with exchanges. This query provides a comprehensive suggestion for refunds, exchanges, and fees, which you can use as input to the `returnProcess` mutation. **Example:** ```graphql query { return(id: "gid://shopify/Return/123") { suggestedFinancialOutcome( returnLineItems: [ { id: "gid://shopify/ReturnLineItem/456", quantity: 1 } ], exchangeLineItems: [ { id: "gid://shopify/ExchangeLineItem/789", quantity: 1 } ] ) { totalReturnAmount { shopMoney { amount currencyCode } } financialTransfer { suggestedTransactions { amountSet { shopMoney { amount currencyCode } } } } // ...other fields } } } ``` For more details, see [Migrate to return processing](/docs/apps/build/orders-fulfillment/returns-apps/migrate-to-return-processing). ## Step 3: Create a return with exchanges Use the [`returnCreate`](/docs/api/admin-graphql/latest/mutations/returnCreate) mutation to create a return with exchange line items. This step records the merchant's intent to process a return and/or exchange, but does **not** confirm the exchange or create fulfillment orders. > Note: > You must process the return using the [`returnProcess`](/docs/api/admin-graphql/unstable/mutations/returnProcess) mutation to confirm the exchange and create fulfillment orders. See the next step. ```graphql mutation returnCreateMutation { returnCreate( returnInput: { # The ID of the order to return. orderId: "gid://shopify/Order/1", # A return shipping fee to apply to this return. returnShippingFee: { amount: { amount: 10, currencyCode: CAD } }, returnLineItems: [ { # The ID of the related fulfillment line item to return. fulfillmentLineItemId: "gid://shopify/FulfillmentLineItem/1", # The number of items that were previously fulfilled. quantity: 1, # The item is returned for another reason. For the `OTHER` value, you must also provide a return reason note. returnReason: OTHER # The additional information about the return reason. returnReasonNote: "I need a bigger size." # A restocking fee, as a percentage, to apply to this return line item. restockingFee: { percentage: 10 }, }, { fulfillmentLineItemId: "gid://shopify/FulfillmentLineItem/2", quantity: 1, # The item is returned because the size was too small. returnReason: SIZE_TOO_SMALL } ], # The exchange items to add to this return. exchangeLineItems: [ { # The product variant ID of the exchange item to add. variantId: "gid://shopify/ProductVariant/1", # The number of items for the exchanged variant quantity: 1 } ] # Send an email to the customer to notify them about the return. # The return is associated to the `Order` object, specified by the `orderId`. # If `notifyCustomer` is set to `true`, then `Order.email` is required. notifyCustomer: true, # The UTC date and time when the customer first requested the return. requestedAt: "2022-05-04T00:00:00Z" } ) { return { id } userErrors { field message } } } ``` ```json { "data": { "returnCreate": { "return": { "id": "gid://shopify/Return/1" }, "userErrors": [] } } } ``` ## Step 4: Process the return to confirm exchanges After creating the return, call the [`returnProcess`](/docs/api/admin-graphql/unstable/mutations/returnProcess) mutation. This step: - Confirms the return and exchange items. - Creates fulfillment orders for the exchange items. - Records the return and exchanges in merchant financial reports. **Example:** ```graphql mutation ReturnProcess { returnProcess( input: { returnId: "gid://shopify/Return/123", returnLineItems: [ { id: "gid://shopify/ReturnLineItem/456", quantity: 1, dispositions: [ { reverseFulfillmentOrderLineItemId: "gid://shopify/ReverseFulfillmentOrderLineItem/789", quantity: 1, locationId: "gid://shopify/Location/101112", dispositionType: RESTOCKED } ] } ], exchangeLineItems: [ { id: "gid://shopify/ExchangeLineItem/131415", quantity: 1 } ], notifyCustomer: true } ) { return { id status } userErrors { field message } } } ``` - Use the output from `suggestedFinancialOutcome` to help populate the input for this mutation. - Only after this step are fulfillment orders for exchanges created and merchant financials updated. For more details and advanced scenarios, see [Migrate to return processing](/docs/apps/build/orders-fulfillment/returns-apps/migrate-to-return-processing). ## Step 5: Collect payment for the exchange If a balance is due on the exchange, use the [`orderInvoiceSend`](/docs/api/admin-graphql/latest/mutations/orderInvoiceSend) mutation to send an invoice to the customer before releasing the hold on the exchange. > Note: > **Only exchanges with a net payable balance due by the buyer are placed on hold.** Even or refundable exchanges are not placed on hold and can be fulfilled immediately after processing. If your exchange does not require additional payment from the buyer, you can skip the steps related to fulfillment holds and proceed directly to fulfillment. ```graphql mutation OrderInvoiceSend { orderInvoiceSend( # The ID of the order to exchange. id: "gid://shopify/Order/1", # The email details. email: { # The email recipient. to: "customer@email.com", # The email sender. from: "merchant ", # The email subject. subject: "Invoice #1092", # The email message. customMessage: "Email message", # The email bcc recipients. bcc: [], # The email cc recipients. cc: [] }) { order { id } userErrors { message } } } ``` ```json { "data": { "orderInvoiceSend": { "order": { "id": "gid://shopify/Order/1", }, "userErrors": [], } } } ``` ## Step 6: Query for the ID of the fulfillment order on hold > Note: > **This step is only required if your exchange fulfillment order is on hold due to a net payable balance.** To fulfill the item for the exchange, you need to release the hold on the fulfillment order by querying for the ID of the fulfillment order that contains the hold. To retrieve the fulfillment order ID, provide the ID of the order in the [`order`](/docs/api/admin-graphql/latest/queries/order) query. The response returns the `fulfillmentOrders` which you can filter to locate the fulfillment order with a `status` of `ON_HOLD` and a `fulfillmentHold` with a `reason` of `AWAITING_RETURN_ITEMS`. You'll use the ID of that `fulfillmentOrder` to [release the hold](#step-7-release-the-hold-in-order-to-fulfill-the-item) in the next step. ```graphql query Order { order(id: "gid://shopify/Order/1") { id fulfillmentOrders (first: 10) { nodes { id status fulfillmentHolds { id reason } } } } } ``` ```json { "data": { "order": { "id": "gid://shopify/Order/1", "fulfillmentOrders": { "nodes": [ { "id": "gid://shopify/FulfillmentOrder/1", "status": "ON_HOLD", "fulfillmentHolds": [ { "id": "gid://shopify/FulfillmentHold/1", "reason": "AWAITING_RETURN_ITEMS" } ] } ] } } } } ``` ## Step 7: Release the hold in order to fulfill the item > **This step is only required if your exchange fulfillment order is on hold.** After a payment has been received, you can use the [`fulfillmentOrderReleaseHold`](/docs/api/admin-graphql/latest/mutations/fulfillmentOrderReleaseHold) mutation to release the item for the exchange so that it can be fulfilled. ```graphql mutation FulfillmentOrderReleaseHold { fulfillmentOrderReleaseHold( # The fulfillment order ID of the fulfillment order that is on hold. id: "gid://FulfillmentOrder/Order/1" ) { order { id } userErrors { message } } } ``` ```json { "data": { "fulfillmentOrderReleaseHold": { "fulfillmentOrder": { "id": "gid://shopify/FulfillmentOrder/1", }, "userErrors": [], } }, } ``` # Step 8: Fulfill the item After the hold has been released (if applicable), or immediately after processing for even or refundable exchanges, you can fulfill the item using the [`fulfillmentOrderSubmitFulfillmentRequest`](/docs/api/admin-graphql/latest/mutations/fulfillmentOrderSubmitFulfillmentRequest) or [`fulfillmentCreateV2` mutation](/docs/api/admin-graphql/latest/mutations/fulfillmentCreateV2) mutation. > Tip: > For most exchanges (even or refundable), you can fulfill the item immediately after processing the return, without any need to check or release a fulfillment hold. ## Next steps - Learn how to [preview and refund duties](/docs/apps/build/orders-fulfillment/returns-apps/view-and-refund-duties) with the GraphQL Admin API. - Learn how to [manage reverse fulfillment orders](/docs/apps/build/orders-fulfillment/returns-apps/manage-reverse-fulfillment-orders) with the GraphQL Admin API. - Learn how to [manage reverse deliveries](/docs/apps/build/orders-fulfillment/returns-apps/manage-reverse-deliveries) with the GraphQL Admin API. ## Legacy Exchange Workflow (Deprecated) > Caution: > The following workflow applies to API versions prior to 2025-07, or if you have not yet migrated to the new return processing flow. > **Legacy behavior is still supported for a limited time, but will be removed in a future API version.** > All new integrations should use the [Return Processing workflow](#step-4-process-the-return-to-confirm-exchanges). In earlier API versions, creating a return with exchange line items using `returnCreate` would immediately confirm the exchange and create fulfillment orders (all placed on hold). No further processing step was required. This behavior is deprecated and will be removed in a future API version. For migration steps, see [Migrate to return processing](/docs/apps/build/orders-fulfillment/returns-apps/migrate-to-return-processing). | Step | Before (Legacy) | After (Return Processing) | |------|-----------------|--------------------------| | Create return with exchanges | `returnCreate` | `returnCreate` | | Confirm exchanges, create fulfillment orders, update financials | (implicit in `returnCreate`) | **Explicit call to `returnProcess` required** | | Calculate financials | `suggestedRefund` | `suggestedFinancialOutcome` | | Hold logic for exchange fulfillment orders | All exchanges on hold | Only net payable exchanges on hold |