Ready to maximize your revenue?Book a Demo
TOPCODE
Graphql

GraphQL vs REST for the Shopify Admin API: when to use which

Last updated on June 2, 2026

GraphQL vs REST for the Shopify Admin API: when to use which

TOPCODE

Shopify's Admin API comes in two flavors: a REST API that's been around since 2007 and a GraphQL API that became generally available in 2019. Both give you full access to merchant data — products, orders, customers, inventory, billing — but they have meaningfully different performance profiles, rate limit models, and developer ergonomics. The choice you make at the start of a project affects how much API time you spend debugging throttling issues two years later. Here's the practical breakdown.

Rate limits: the most important difference

REST uses a call-based rate limit: 2 requests per second with a burst bucket of 40 requests. That sounds generous until you're paginating through 10,000 products. Each page of 250 products costs one REST call, meaning a full catalog sync costs 40 API calls minimum — which blows through the burst bucket and then proceeds at a steady 2 req/sec. A 10,000-product catalog sync takes roughly 20 seconds of wall clock time just waiting on rate limit headroom.

GraphQL uses a cost-based rate limit: each query has a calculated cost based on the number of objects it could return, and you have a budget of 1,000 points that refills at 50 points per second. A query for 250 products with their 5 images each might cost 1,500 points — over budget for a single call. But a query for 50 products with 2 images each might cost only 100 points. Understanding the cost model up front lets you tune your queries to stay well within budget. The throttleStatus field in every response shows your current available points.

For background jobs that need to process large datasets, both APIs now support bulk operations via GraphQL's bulkOperationRunQuery and bulkOperationRunMutation — which effectively bypass per-request rate limits by running operations asynchronously and returning a JSONL file. There is no REST equivalent. If you're writing an import/export tool or a data migration script, GraphQL bulk operations are the only real option.

Data shape and over-fetching

The REST product endpoint (GET /admin/api/2024-04/products.json) returns the full product object every time: title, handle, body_html, all variants, all images, all options, all metafields if you request them. If you only need product IDs and titles for a dropdown, you're still receiving several kilobytes of JSON per product and paying the full rate limit cost. This over-fetching compounds at scale.

GraphQL solves this precisely. You declare exactly which fields you need in your query, and Shopify returns only those fields. Fetching 50 product IDs and titles costs almost nothing in terms of bandwidth and query cost, compared to fetching the equivalent via REST with fields= filtering (which REST does support, to be fair, but is not the default workflow).

queries/getProductTitles.graphqlgraphql
# GraphQL — fetch only what you need
query GetProductTitles($cursor: String) {
  products(first: 50, after: $cursor) {
    edges {
      node {
        id
        title
        status
      }
      cursor
    }
    pageInfo {
      hasNextPage
    }
  }
}

Mutations vs write endpoints

For writes, the gap between REST and GraphQL is smaller in terms of ergonomics, but meaningful in error handling. REST returns HTTP status codes and a generic errors array. GraphQL mutations return a typed userErrors array specific to each mutation, with codes like INVALID_PRICE, VARIANT_ALREADY_EXISTS, or INVENTORY_QUANTITY_EXCEEDS_AVAILABLE. These typed errors make client-side form validation far easier — you can map userErrors directly to field-level messages.

The productCreate mutation, for example, returns { product { id title } userErrors { field message code } }. You can pattern-match on the code in your handler and show a localized error message without needing to parse a string. REST's { errors: { title: ['can't be blank'] } } requires string matching, which breaks if Shopify changes the wording.

GraphQL Admin API

Pros

  • Cost-based rate limits reward efficient queries
  • Fetch exactly the fields you need — zero over-fetching
  • Typed userErrors on mutations make error handling reliable
  • Bulk operations for large dataset jobs (no REST equivalent)
  • Schema introspection: tooling like graphql-codegen generates TypeScript types automatically
  • Webhooks use GraphQL topics like PRODUCTS_CREATE — consistent naming

Cons

  • Steeper learning curve for teams new to GraphQL
  • Query cost model requires upfront planning to avoid 429s
  • Some legacy resources (e.g. older fulfillment APIs) are REST-only or GraphQL-lagging
  • Tooling setup (Apollo Client, urql, or raw fetch with gql) adds initial boilerplate

When REST still makes sense

REST is not going away. Shopify has committed to maintaining both APIs, and some endpoints exist only in REST — notably the ScriptTag resource, the deprecated Discount resource (pre-Shopify Functions), and several fulfillment-related endpoints that are still being ported. If you're building an integration on top of an existing codebase that already uses the REST API, the migration cost often outweighs the benefits for simple apps.

REST is also a reasonable choice if your app makes only a handful of API calls per page load and doesn't need bulk operations. A simple app that reads the shop name, fetches the last 10 orders, and updates a single product field will not hit REST rate limits under normal usage. The complexity cost of setting up a GraphQL client isn't justified for that use case.

The practical recommendation

Start new apps with GraphQL. Shopify is investing heavily in the GraphQL API — new capabilities (Selling Plan Groups, Product Feeds, Markets, B2B) appear in GraphQL months before or instead of REST. Using GraphQL from day one means you won't hit a ceiling where the feature you need doesn't have a REST endpoint.

Use the Admin GraphQL API reference alongside the API's built-in cost calculator (visible in the GraphiQL explorer in your Partner dashboard) to plan your queries before writing production code. Pay particular attention to connection fields — every connection adds cost proportional to the first: N argument.

Pagination patterns compared

REST pagination uses page-based or link-based patterns. GET /admin/api/2024-04/products.json?limit=250&page_info=<cursor> uses a Link header to provide the next and previous page cursors. You must parse the Link header on each response, which is non-obvious and error-prone. The limit is capped at 250 objects per page. Once you've retrieved a page, you can't go back and fetch it from a different page number — it's cursor-only forward pagination.

GraphQL uses Relay-style cursor pagination: the connection type has a pageInfo object with hasNextPage, hasPreviousPage, startCursor, and endCursor. You pass after: $cursor to get the next page. This is the same pattern used by GitHub's API, Stripe's API, and many other modern GraphQL APIs — your team has likely seen it before. Hydrogen's getPaginationVariables helper and the Pagination component abstract it further so you rarely write pagination logic manually.

Webhooks and the API surface

Shopify webhooks are related to your API choice in a subtle way. Webhook subscription topics like PRODUCTS_CREATE and ORDERS_UPDATED are defined in the GraphQL schema. When you register webhooks via the Admin GraphQL API using the webhookSubscriptionCreate mutation, you specify the topic as a GraphQL enum value. REST webhook registration (POST /admin/api/webhooks.json) uses string topics like 'products/create'. Both methods deliver the same payload, but the GraphQL method gives you more control — you can specify a sub-selection of fields to receive in the payload using the includeFields parameter, reducing the size of the webhook body for high-volume webhooks.

For apps that process webhooks at scale — an order management app receiving thousands of orders/created events per day — the ability to filter webhook payload fields via GraphQL webhooks can meaningfully reduce processing time and payload size. A typical orders/paid webhook payload is 8–15KB of JSON. If your handler only needs the order ID, customer email, and line item IDs, you can reduce that to under 1KB with field selection.

TypeScript code generation: a GraphQL superpower

One of the most underappreciated advantages of GraphQL over REST is the ability to auto-generate TypeScript types from your queries. Tools like graphql-codegen or Shopify's own @shopify/api-codegen-preset read your .graphql files and generate precise TypeScript interfaces for every query result and mutation input. The generated types exactly match what the API returns — not a generic ApiResponse<Product>, but a type that has the exact fields you requested.

REST APIs can publish OpenAPI/Swagger specs that tooling can consume, and Shopify does publish a REST OpenAPI spec. However, REST type generation is less ergonomic: it generates types for the entire resource even if you only use three fields, and the REST API's response wrappers (the { products: [...] } envelope) add an extra layer that GraphQL doesn't have. In practice, most TypeScript-heavy Shopify teams use GraphQL codegen to eliminate an entire class of 'property does not exist on type' errors that plague REST-based integrations written without strict type checking.

Frequently asked questions

Is Shopify deprecating the REST Admin API?

Not imminently. As of 2026, Shopify has stated that the REST API will continue to be supported, but new capabilities are being added exclusively to GraphQL. Several REST endpoints (like the legacy Redirect resource and some report endpoints) are in maintenance mode. Shopify recommends all new development use GraphQL, and the overall trajectory is clearly toward GraphQL-only — but the migration deadline is not announced.

Can I mix REST and GraphQL calls in the same app?

Yes — they use the same OAuth access token and the same rate limit bucket (though they have separate rate limit counters). A common migration pattern is to use GraphQL for new features while leaving existing REST calls in place until you have time to migrate them. Both are authenticated with the same Authorization: Bearer <token> header.

Founder & Engineer

Rico Tan

Keep reading