Web/Guru

Moving Your Shopify Integration from REST to the GraphQL Admin API (2026 Guide)

Shopify's REST Admin API is legacy. How to move an integration to GraphQL: IDs, cost-based limits, bulk operations and a staged plan, with JS code.

Published
Updated
October 5, 2026
Read time
8 min read
Written by
Webguru
Moving Your Shopify Integration from REST to the GraphQL Admin API (2026 Guide)
/On this page01 / 10

If your ERP sync, warehouse connector or in-house script still talks to Shopify's REST Admin API, it's running on a legacy API. Shopify made REST legacy on October 1, 2024. Since April 1, 2025, new public apps must be built only on the GraphQL Admin API. The REST endpoints for products and variants were deprecated in API version 2024-04, and public apps had to move to the GraphQL product APIs by February 1, 2025.

As of this writing, Shopify hasn't published a shutdown date for the remaining REST endpoints used by existing apps. That's not a reason to wait. New platform features ship in GraphQL, and the product model already works around REST's limits. This guide covers what actually changes when you move an integration, and a migration plan that doesn't break production.

Why move now, even without a deadline

  • Products and variants. REST product endpoints are deprecated, and custom apps need the GraphQL product APIs to handle products with more than 100 variants.
  • New features land in GraphQL. Shopify focuses new development on the GraphQL Admin API, and many newer platform features have no REST equivalent.
  • Fewer round trips. One GraphQL query can fetch a product with its variants, inventory and metafields, which took several REST calls.
  • Bulk exports. Large catalog or order exports run as one asynchronous bulk operation instead of thousands of paginated requests.
  • Fewer surprises. Deprecated REST calls tend to surface as late-night failures after an API version change. Moving on your own schedule is cheaper.

What changes when you switch

  • One endpoint. Every request is a POST to https://{shop}.myshopify.com/admin/api/2026-10/graphql.json with a query and variables. Keep pinning a stable API version and upgrade on a schedule.
  • IDs become global IDs. GraphQL uses IDs like gid://shopify/Product/10079785100. Many objects also expose legacyResourceId, the numeric ID your ERP already stores. Keep a mapping, and don't split strings in several places.
  • Cursor pagination. Use first or last with after or before, and read pageInfo { hasNextPage endCursor }. Array inputs are capped at 250 items on every Shopify API, and paginating a list stops at 25,000 objects, so large exports belong in bulk operations.
  • Rate limits by cost, not request count. Each query has a calculated cost; a single query can't exceed 1,000 points. Every response includes extensions.cost with requestedQueryCost, actualQueryCost and a throttleStatus: maximumAvailable, currentlyAvailable and restoreRate. Throttle from those numbers rather than from a fixed sleep.
  • Errors come back with HTTP 200. A mutation that fails validation still returns 200, with the problem listed in userErrors. Check errors (malformed queries, throttling) and userErrors on every call.

A cost-aware paginated fetch

js
async function shopifyGraphQL(shop, token, query, variables = {}) {
  const res = await fetch(`https://${shop}/admin/api/2026-10/graphql.json`, {
    method: "POST",
    headers: { "Content-Type": "application/json", "X-Shopify-Access-Token": token },
    body: JSON.stringify({ query, variables }),
  });
  const body = await res.json();
  if (body.errors) throw new Error(JSON.stringify(body.errors));
  // Wait if the bucket is nearly empty, based on Shopify's own numbers
  const t = body.extensions?.cost?.throttleStatus;
  if (t && t.currentlyAvailable < 200) {
    const wait = ((200 - t.currentlyAvailable) / t.restoreRate) * 1000;
    await new Promise((r) => setTimeout(r, wait));
  }
  return body.data;
}
const PRODUCTS = `query Products($cursor: String) {
  products(first: 100, after: $cursor) {
    nodes { id legacyResourceId title variants(first: 100) { nodes { id sku price } } }
    pageInfo { hasNextPage endCursor }
  }
}`;
async function allProducts(shop, token) {
  const out = [];
  let cursor = null;
  do {
    const { products } = await shopifyGraphQL(shop, token, PRODUCTS, { cursor });
    out.push(...products.nodes);
    cursor = products.pageInfo.hasNextPage ? products.pageInfo.endCursor : null;
  } while (cursor);
  return out;
}

Keep page sizes moderate, because nested connections multiply the query cost. For full-catalog syncs, use a bulk operation instead of looping.

Always check userErrors on mutations

js
const UPDATE_PRICE = `mutation UpdatePrice($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
  productVariantsBulkUpdate(productId: $productId, variants: $variants) {
    productVariants { id price }
    userErrors { field message }
  }
}`;
const data = await shopifyGraphQL(shop, token, UPDATE_PRICE, {
  productId: "gid://shopify/Product/10079785100",
  variants: [{ id: "gid://shopify/ProductVariant/43729076", price: "24.99" }],
});
const { userErrors } = data.productVariantsBulkUpdate;
if (userErrors.length) throw new Error(userErrors.map((e) => `${e.field}: ${e.message}`).join("; "));

Large exports: bulk operations

For "every product", "every order this year" or "all inventory levels", start a bulkOperationRunQuery. Shopify runs the query asynchronously and gives you a JSONL file, one JSON object per line. Subscribe to the bulk_operations/finish webhook, or poll the operation's status. From API version 2026-01, an app can run up to five bulk query operations per shop at the same time; earlier versions allowed one. A bulk query must finish within 10 days.

What changed in API version 2026-10

Shopify released API version 2026-10 on October 1, 2026. Three of its changes matter for integrations that are moving to GraphQL:

  • ProductVariant.barcode is deprecated. Variants now have a barcodes connection that holds up to 20 typed barcodes, and the old field returns only the first one. ERP and warehouse syncs should read and write barcodes.
  • Invalid metafield filters now return an error. If a query filters by a metafield that isn't valid for filtering, the GraphQL Admin API now fails the query instead of silently ignoring that predicate. Check the filter definitions your searches rely on.
  • Updating an order's shipping address recalculates tax. After an address change, re-read taxLines, totalTaxSet and the order totals, and handle any payment or refund difference.

The full list is in Shopify's 2026-10 release notes. We also track version and platform deadlines on our Shopify deprecation tracker.

A migration plan that doesn't break production

  1. Inventory every REST call. Search the codebase for /admin/api/ and .json endpoints, and log the paths your integration calls in production for a week. Custom apps can also list deprecated calls from the last 30 days through the Deprecated API calls endpoint, and Shopify reports deprecated usage in the X-Shopify-API-Deprecated-Reason header and your app's API health report.
  2. Migrate by resource, not all at once. Products and variants go first because they're already deprecated, then orders, inventory and customers.
  3. Wrap the client once. One module handles auth, the version, cost-based throttling, retries and userErrors, so no call site talks to the API directly.
  4. Add an ID translation layer. Map legacyResourceId values to GIDs and store both during the transition.
  5. Shadow-run. For read paths, call REST and GraphQL side by side and compare the results before switching.
  6. Switch writes last, one resource at a time, with alerts on userErrors.
  7. Pin and schedule versions. Put the next API version upgrade on the calendar and check the changelog before each quarter's release.

If your integration still runs on an admin-created legacy custom app, read our guide to custom vs public apps first, because the app type decides how it gets an access token.

If your integration is a public app, remember the separate deadline for expiring offline access tokens on January 1, 2027 (our migration guide). It's efficient to do both in the same release.

Common mistakes

  • Treating HTTP 200 as success. Check userErrors on every mutation.
  • Paginating huge datasets. Use bulk operations for full exports.
  • Assuming numeric IDs. GraphQL returns GIDs, so map them explicitly.
  • Fixed sleeps between calls. Throttle using throttleStatus, which is both faster and safer.
  • Waiting for a sunset date. The product endpoints were already deprecated, and new features won't come to REST.

If your integration also listens to webhooks, check it against our guide to Shopify webhooks you can trust: verification, duplicates, retries and reconciliation.

Connecting an ERP? We compare the options for NetSuite and Business Central separately.

Want it migrated for you?

We move ERP, WMS and custom integrations from REST to the GraphQL Admin API. That includes an audit of your current calls, a shared client with cost-based throttling, ID mapping, shadow runs and a staged cutover. Tell us about your integration and we'll reply with a scoped plan. For app builds and other platform work, see our Shopify development services.

Sources

/Tags#Shopify#Admin API#Integrations

Written by

Webguru

Shopify Development Team

Webguru.pk is a Shopify development team in Lahore, Pakistan, building stores, custom apps and integrations for brands worldwide.

/Share

Need a hand with this?

We design, build and fix this kind of work for Shopify, SaaS and AI products.

/faqs

REST to GraphQL Migration: Frequently Asked Questions

The REST Admin API has been legacy since October 1, 2024, and new public apps have had to use the GraphQL Admin API since April 1, 2025. The REST product and variant endpoints were deprecated in API version 2024-04. At the time of writing, Shopify hasn't published a shutdown date for the remaining REST endpoints used by existing apps.

Custom apps aren't bound by the April 2025 rule for new public apps, but they're affected by the product API deprecation and need the GraphQL product APIs to handle products with more than 100 variants. New platform features also land in GraphQL.

GraphQL uses global IDs such as gid://shopify/Product/10079785100. Many objects also expose legacyResourceId, the numeric ID used by REST, so you can keep a mapping between the two during the migration.

GraphQL limits are based on calculated query cost rather than request count, and a single query can't exceed 1,000 points. Each response includes extensions.cost with the requested and actual cost and a throttleStatus showing available points and the restore rate.

Use a bulk operation with bulkOperationRunQuery. Shopify runs it asynchronously and returns a JSONL file; you can subscribe to the bulk_operations/finish webhook or poll the status. From API version 2026-01 an app can run up to five bulk queries per shop at once.

Still calling the REST Admin API?

We audit your REST calls and move the integration to GraphQL in stages, with shadow runs before any write path switches.

or write to+44 7882 755642