← All provider guides

Shopify GraphQL API migration: four deprecation waves, four codemods

shopify graphql migration 55 tracked change records

Shopify does not break its APIs in big SDK majors. It retires surface on a dated-version schedule — 2026-07, 2026-10 — and the changes land inside GraphQL documents rather than SDK method names: a field renamed on the Customer Account API, an entire query subtree removed, a deprecated mutation argument, a new enum value that existing exhaustive switches do not handle, and a POS UI extensions print call replaced by a namespaced successor. Each of these breaks consuming code in a way a lockfile bump never mentions, and each is mechanical enough to carry as a codemod. mendapi ships four deterministic packs for these waves, all applied locally as reviewable diffs.

The four packs, exactly as the registry ships them

This list is read directly from the mendapi migration pack registry at page build time, so it matches what the codemods actually do:

Shopify Customer Account API: discountedUnitPrice -> approximateDiscountedUnitPrice

Pack shopify-customer-account-draftorder-price:

  • Rename discountedUnitPrice field to approximateDiscountedUnitPrice in Customer Account API GraphQL usage

Upstream changelog: https://shopify.dev/changelog/discountedunitprice-on-draftorderlineitem-customer-account-api-deprecation

Shopify Customer Account API: remove Customer.lastIncompleteCheckout selections (Checkout subtree removed in 2026-10)

Pack shopify-customer-last-incomplete-checkout:

  • Delete lastIncompleteCheckout selections (including the nested Checkout selection block) from Customer Account API GraphQL queries

Upstream changelog: https://shopify.dev/changelog/customer-account-api-customer-lastincompletecheckout-and-checkout-types-removed

Shopify Admin API: drop deprecated isCumulative argument on marketingEngagementCreate

Pack shopify-marketing-engagement-cumulative:

  • Remove the deprecated cumulative flag from marketingEngagementCreate calls (GraphQL field, variable declaration, and JS variables object)

Upstream changelog: https://shopify.dev/changelog/deprecation-of-cumulative-marketing-engagements

Shopify Admin API: handle new OrderDisplayFulfillmentStatus value FULFILLMENT_NOT_REQUIRED (2026-10)

Pack shopify-order-fulfillment-not-required:

  • Insert case 'FULFILLMENT_NOT_REQUIRED' as a fall-through before case 'UNFULFILLED' in switches over displayFulfillmentStatus
  • Insert a FULFILLMENT_NOT_REQUIRED entry mirroring the UNFULFILLED entry in status-to-value object maps

Upstream changelog: https://shopify.dev/changelog/orderdisplayfulfillmentstatus-now-returns-fulfillment_not_required

Preview each migration as one diff

Every pack runs locally as a dry run by default. On the bundled fixture repos each pack rewrites the affected GraphQL documents and call sites, writing a unified patch you can read before anything is applied:

npx mendapi sync
# -> one network call: pulls the change feed into a local database
npx mendapi scan
# -> which files touch the affected Shopify API surfaces, with line numbers
mendapi fix --migration shopify-customer-account-draftorder-price
# -> dry run: changes.patch + fix-report.json, nothing applied
mendapi fix --migration shopify-customer-account-draftorder-price --apply
# -> applies the reviewed diff

Nothing leaves your machine at any step; zero network calls is the default and only mode. The report records which rule fired where, and every rewritten file carries a syntax-check verdict, so review is a read of the patch, not an audit of the tool.

The diff itself, byte for byte

This is the actual changes.patch the Checkout-subtree removal pack (shopify-customer-last-incomplete-checkout) produces in a dry run on the bundled Customer Account API fixture — embedded here from the golden regression evidence at page build time, not retyped. It is a pure-deletion patch, which is exactly what the changelog asks for: the deprecated lastIncompleteCheckout selection already returned null, so the mend deletes the selection from queries. The removal is brace-balanced, so the whole nested block — appliedGiftCards, its balance money fields, the lineItems connection — goes with the parent field in one piece, and the single-line form (lastIncompleteCheckout { id totalPrice { amount } }) and the bare field form are both handled. Note what the codemod leaves alone: every sibling selection — displayName, defaultAddress, emailAddress, tags — survives as untouched context, because the rule anchors on the removed field's selection subtree, not on line proximity.

--- a/index.js
+++ b/index.js
@@ -8,19 +8,6 @@
     customer {
       id
       displayName
-      lastIncompleteCheckout {
-        id
-        appliedGiftCards {
-          id
-          balance { amount currencyCode }
-        }
-        lineItems(first: 10) {
-          nodes {
-            title
-            quantity
-          }
-        }
-      }
       defaultAddress {
         city
         country
--- a/lib/recovery.js
+++ b/lib/recovery.js
@@ -6,7 +6,6 @@
   query RecoveryCandidates {
     customer {
       id
-      lastIncompleteCheckout { id totalPrice { amount } }
       emailAddress {
         emailAddress
       }
@@ -18,7 +17,6 @@
   query CustomerFlags {
     customer {
       id
-      lastIncompleteCheckout
       tags
     }
   }

What the codemods refuse to touch

A migration tool earns trust by what it leaves alone. GraphQL rewrites here anchor on the affected API's document shape, not on bare identifiers: a discountedUnitPrice field on an unrelated object is never renamed, and the fulfillment-status pack only inserts the new FULFILLMENT_NOT_REQUIRED case where an exhaustive switch or status map over displayFulfillmentStatus already exists — it does not invent handling logic. Every rule is locked by golden-fixture regression tests with negative controls (fixture lines that must survive byte-for-byte), so a rule that starts over-firing fails our build before it reaches your repo.

The part no codemod fixes

Honest scope: the packs handle the mechanical rewrites. They do not decide what your checkout flow should do now that the Checkout subtree is gone, whether your analytics should recompute historical marketing engagements as non-cumulative, or how mTLS certificates get provisioned for the card deposit endpoint. The tracked Shopify history counts 20 breaking or deprecation entries, and the platform-behavior ones stay not code-fixable by verdict — the Shopify tracker lists every verdict with its reason — because pretending a codemod covers them is how migrations go wrong.

Related

Every entry on this page is generated from the mendapi change database at build time and links its upstream source.