Shopify GraphQL vs REST API: A Comprehensive Developer's Guide
Compare Shopify's GraphQL and REST APIs to decide which is best for your app. Learn about performance, rate limits, and efficiency.

Start Here: REST Admin Is Legacy Now
If you're picking an API for a new Shopify integration in 2025, the decision is mostly made for you. Shopify froze the REST Admin API at version 2024-07 and now treats it as legacy. New resources, new fields, new features land in the GraphQL Admin API first, and a chunk of them never make it to REST at all. I've shipped against both APIs daily for years, and the trend has been one-directional: GraphQL gets the new toys, REST gets a maintenance flag.
So this isn't really "which is better." It's "where can I still get away with REST, and where do I have no choice but GraphQL." That's the question I actually answer on client projects, so that's how I'll frame this.
The Actual Difference (Briefly)
REST gives you a URL per resource. Want a product, you hit /admin/api/2024-07/products/{id}.json and get the whole object back. Want its variants and metafields, you're often making more calls.
GraphQL gives you one endpoint, /admin/api/2025-01/graphql.json, and you describe the exact shape you want. You ask for a product's title and its first three variants' SKUs, that's what comes back. Nothing extra.
That single difference drives almost everything below: payload size, rate limits, the N+1 problem, all of it.
Over-fetching and the N+1 Tax
This is where REST hurts on real stores, not test stores.
With REST, if I need the order list plus each customer's name, the order payload gives me a customer_id and that's it. So now I'm looping orders and firing a /customers/{id} call per order. Ten orders, eleven requests. On a store with real volume that loop throttles hard, and it usually passes QA because QA runs against five orders.
GraphQL collapses that into one query:
query RecentOrders {
orders(first: 10, sortKey: CREATED_AT, reverse: true) {
nodes {
id
name
customer {
displayName
}
}
}
}One request, one payload, exactly the fields I asked for. The N+1 loop is gone because the relationship is part of the query, not something I stitch together client-side.
Rate Limiting: Two Completely Different Models
This is the part people get wrong when they port REST code to GraphQL and wonder why it behaves differently.
REST is request-counted. Standard plans get a leaky bucket of roughly 2 requests/second, 40 in the bucket. Blow past it, you get a 429. Simple to reason about, but it punishes you for needing data from many resources, because every resource is another request against the same small ceiling.
GraphQL is cost-based. Every field and connection in your query has a point value, and you get a bucket of points (1000 for standard, restoring at 50/sec) instead of a request count. A cheap query costs a few points. A deep nested one costs a lot. The response even tells you what it cost:
"extensions": {
"cost": {
"requestedQueryCost": 92,
"actualQueryCost": 47,
"throttleStatus": {
"maximumAvailable": 1000,
"currentlyAvailable": 953,
"restoreRate": 50
}
}
}I read throttleStatus on every response and pace off currentlyAvailable, not off a sleep(500). That's the whole game on high-volume syncs. And the practical upside is real: I can pull a pile of related data in one well-shaped query that would've been a dozen throttled REST calls.
Bulk Operations: The Thing REST Just Doesn't Have
If you're exporting every product, order, or customer on a large store, stop thinking about pagination loops entirely. GraphQL has bulk operations. You submit a query, Shopify runs it asynchronously against the whole dataset, and hands you a JSONL file URL when it's done. No cursor babysitting, no cost throttling mid-export.
mutation {
bulkOperationRunQuery(
query: """
{
products {
edges {
node { id handle updatedAt }
}
}
}
"""
) {
bulkOperation { id status }
userErrors { field message }
}
}You poll currentBulkOperation until status is COMPLETED, then download the file. For anything north of a few thousand records this is the correct tool, and REST has no equivalent. This alone has decided "GraphQL" for me on most data migration jobs.
Webhooks, Versioning, Tooling
Webhooks still work on both, but note the payloads are diverging. Shopify is steering new webhook work toward GraphQL-style topics and you'll want webhook payloads that line up with your GraphQL data model anyway. Keeping read API and webhook shape consistent saves a lot of mapping code.
Versioning is date-based on both (2025-01, 2024-07), but GraphQL deprecates at the field level instead of forcing a whole-endpoint jump, and the schema is introspectable. I can query the schema itself and see exactly what's deprecated and what replaced it. If you're using the Shopify Dev MCP server, that introspection plus live doc search is genuinely useful for catching deprecations before they bite in production.
Tooling is where GraphQL pulls ahead once you're past the initial setup. REST you can poke with curl in ten seconds, true. But GraphiQL gives you autocomplete against your store's real schema, cost preview, and a typed contract that kills a class of "field doesn't exist" bugs before runtime. When I'm building anything with deep nested filtering, that typed exploration is most of why the code ends up correct. If filtering is your bottleneck, I went deep on the query syntax in my Shopify search and filtering guide.
The Verdict: When REST Is Still Fine
I don't rewrite working REST code for the sake of it. Here's my actual rule.
Use GraphQL when:
- It's a new Admin API integration. Full stop, this is the default now.
- You need nested data: products to variants to metafields, orders to customers to line items.
- You're doing large exports. Bulk operations, every time.
- Rate limits are a real constraint and you want to pace off query cost.
REST is still fine when:
- It's a tiny one-off script and the endpoint you need still exists in REST. A 20-line inventory bump doesn't need a GraphQL client.
- You're maintaining a legacy app on
2024-07and a full data-layer rewrite isn't justified yet. Keep it running, migrate the hot paths.
What's no longer a valid reason for REST: "the field isn't in GraphQL." That used to be true for some resources. It's now the rare exception, and it's shrinking every release.
How I Audit an Existing Integration
When a client hands me a codebase that's throttling, this is the pass I make:
- Is each endpoint/mutation even available in GraphQL yet? Usually yes.
- What's the query cost and pagination shape? Cursor-based, never offset.
- Would a bulk operation replace this whole loop?
- Are we fetching full objects when the job needs five fields?
- Are retries built around
throttleStatus, or are they blindsleepcalls? Almost always the latter, almost always the bug.
Production Failure Modes
The bug, nine times out of ten, is over-fetching plus no backoff. REST code that loops products, variants, metafields, inventory, and orders runs clean in testing and falls over on a live store. GraphQL fails the same way if the query is too expensive or pagination gets ignored. Specifically what bites:
- Pulling every field instead of the five you use.
- Ignoring
requestedQueryCostvsactualQueryCost. - Offset pagination still lurking in old REST code.
- Per-record mutations where a batch or bulk op would do.
- No retry/backoff keyed to throttle responses.
My Sync-Job Starting Point
This is the shape I reach for on read syncs: small field set, cursor pagination, only the data needed to decide the next operation.
query ProductsForSync($cursor: String) {
products(first: 50, after: $cursor) {
pageInfo { hasNextPage endCursor }
nodes { id handle updatedAt }
}
}I don't ask for variants or metafields here. I ask for what I need to know which products changed, then go fetch detail only for those. Cheap query, predictable cost, easy to retry.
What I Ship First
On a client integration I ship a small read-only GraphQL sync with cost and throttle logging before touching the old REST calls. That gives the team real numbers on rate limits, pagination, and error handling instead of guesses. Then:
- Read-only sync first, logging
costandthrottleStatus. - Bulk operations for the big exports.
- Keep REST only where it's genuinely the practical path.
- Pin API versions, review deprecations every quarter.
If you just want to sanity-check an approach or get a query reviewed without all this, I built Ask Shopify for exactly that kind of quick gut-check.
FAQ
Is the REST Admin API being shut down? Not deleted, but frozen. It's stuck at version 2024-07 and marked legacy. New features go to GraphQL. Existing REST calls keep working, but don't build anything new on it.
Do I have to migrate my existing REST app right now? No. If it works and isn't throttling, leave it. Migrate the hot paths and large exports to GraphQL first, where the payoff is real.
Which is faster? For a single trivial fetch, REST is fine and arguably simpler. For anything nested or high-volume, GraphQL wins because it's one request, a smaller payload, and a higher effective throughput under cost-based limits.
What about bulk exports of a whole store? GraphQL bulk operations, no contest. REST has nothing equivalent and you'll spend the project fighting pagination and rate limits.
Sources
- Shopify API rate limits: https://shopify.dev/docs/api/usage/limits
- REST Admin API (legacy) rate limits: https://shopify.dev/docs/api/admin-rest/usage/rate-limits
Want this built for you instead of DIY?
I'm Karan โ a Top Rated Plus Shopify Expert ($300K+ earned, 100% Job Success). If you'd rather hand this to someone who's done it hundreds of times, let's talk.
๐ ๏ธShopify Development Tools You Might Like
Tags
๐ฌ Get notified about new tools & tutorials
No spam. Unsubscribe anytime.
Comments (0)
Leave a Comment
No comments yet. Be the first to share your thoughts!


