Why Shopify Admin GraphQL Rate Limits Hit Harder Than Developers Expect
Every Shopify developer eventually stares at the same cryptic response: "errors": [{"message": "Throttled"}]. The app was working yesterday. The query looks reasonable. Nothing obvious broke. But somewhere in the gap between "it runs in GraphiQL" and "it runs in production at scale," Shopify's rate limiter stepped in and said no.
The Shopify Admin GraphQL rate limits in 2026 are not a single number you can memorize. They are a cost-based system that scores every query, tracks a per-app-per-shop point bucket, and refuses execution the moment you dip below the requested cost. The docs describe the mechanics in a few paragraphs, but they leave out the pieces that actually matter when you are writing production code: how to read the extensions.cost object, when to reach for bulk operations, how to back off cleanly, and how to structure queries so you stop fighting the limiter in the first place.
This guide is a practical 2026 reference for developers building on the Shopify Admin API. If you want a broader tour of the API surface first, start with our guide to the Shopify Admin API, then come back here to go deep on rate limits. Everything below assumes you are on the 2026-01 or later API version and using the GraphQL Admin API — the REST Admin API is sunsetting for public apps, and Shopify has been clear that GraphQL is the direction of travel.
How the Cost-Based System Works
Forget request counts. The GraphQL Admin API measures load in cost points, and every field you select has a price tag. Shopify's rate limiter uses a leaky bucket that refills at a steady rate, drains when you run queries, and throws back a THROTTLED error when you try to spend more than you have.
Requested Cost vs Actual Cost
When you send a query, Shopify runs two cost calculations:
- Requested cost — a static analysis of your query before it executes. Shopify looks at the fields you asked for and the maximum connection sizes (
first: 250, for example) and assumes the worst case. Your bucket must hold at least this much, or the query is rejected immediately. - Actual cost — what the query actually consumed during execution. If you asked for
first: 250but only 12 products matched, the actual cost is far lower.
After execution, Shopify refunds the difference between requested and actual to your bucket. This is why a query that "should" cost 500 points often drains only 20 — but it is also why queries with no pagination limits get rejected before they ever run. Shopify's engineering team wrote a thorough breakdown of the design in their rate limiting GraphQL APIs post that is still the best conceptual reference.
What Each Field Costs
Point costs are deterministic. You can predict them before shipping:
- Scalar fields (name, id, email, string, int) — 0 points each
- Object fields (a single related object like
order.customer) — 1 point - Connection fields (paginated lists like
products,orders,variants) — 2 points plus 1 point per returned item - Mutations — 10 points each
- Bulk operations — 10 points to start, then free while the async job runs
A query asking for 250 products with 100 variants each and every variant's inventory level can blow past 1,000 points without anyone realizing it. And 1,000 points is the hard ceiling on any single query, regardless of plan.
Standard, Advanced, and Plus: The Three Bucket Sizes
Shopify gives you a bigger bucket if you pay for a bigger plan. In 2026 the numbers your app sees per shop are:
| Plan | Bucket size | Restore rate | Max points per minute |
|---|---|---|---|
| Basic / Grow / Shopify | 2,000 | 100/sec | 6,000 |
| Advanced | 4,000 | 200/sec | 12,000 |
| Shopify Plus | 20,000 | 1,000/sec | 60,000 |
| Shopify for Enterprise | Custom | Custom | Custom |
A few things to notice. First, every plan caps a single query at 1,000 points — Plus stores get a deeper bucket, not bigger queries. Second, the restore rate is per-second, not per-minute, which matters for how you think about parallelism. Third, these limits are per-app-per-store. If five apps are hitting the same Plus store, each app has its own 20,000-point bucket. The official Shopify API limits reference is the source of truth if any numbers shift between API versions.
What This Means in Practice
A Basic plan store refills 100 points every second. That is enough to run one typical order query per second, or a product update mutation every two seconds, without throttling. Pull that same app into a Plus merchant doing Black Friday volume, and you suddenly have 1,000 points per second — enough headroom to parallelize inventory syncs across 10 warehouses without any of them stepping on each other.
This is also why the Shopify Plus plan matters for any app that needs to move serious data. For B2B apps, ERP connectors, and high-volume marketplace integrations, the bucket size is often the deciding factor between a product that works and a product that times out.
Reading the Extensions Cost Object

Every GraphQL response Shopify returns includes an extensions field with a full snapshot of your rate limit state. Your app should read this on every request. If it is not reading extensions.cost, it is flying blind.
{
"data": { ... },
"extensions": {
"cost": {
"requestedQueryCost": 270,
"actualQueryCost": 32,
"throttleStatus": {
"maximumAvailable": 2000,
"currentlyAvailable": 1968,
"restoreRate": 100
}
}
}
}Four fields carry almost all the signal you need:
- requestedQueryCost — what the query was billed against your bucket. This is the number that triggers throttling.
- actualQueryCost — what the query really used. If this is much lower than requested, you are probably over-paginating.
- currentlyAvailable — your remaining bucket capacity right now. Drop below the requested cost of your next query and you will get throttled.
- restoreRate — points per second returning to your bucket. Use this to calculate how long to wait before retrying.
The Cost Debug Header
If you want to see which specific fields drove the bill, add the header Shopify-GraphQL-Cost-Debug: 1 to your request. Shopify will include a per-field breakdown in the extensions block. Use this in development, not production — the debug data makes responses larger, and the full per-field breakdown can be noisy at scale. Lunar's developer guide to Shopify rate limits has good examples of reading debug output to isolate expensive fragments.
Handling a THROTTLED Response
When your app spends more than it has, Shopify returns a 200 OK with a THROTTLED error in the response body. Yes — a 200, not a 429. This is a common trap for developers who only check HTTP status codes. Always inspect the GraphQL errors array.
The response looks like this:
{
"errors": [{
"message": "Throttled",
"extensions": {
"code": "THROTTLED",
"cost": {
"requestedQueryCost": 500,
"throttleStatus": {
"maximumAvailable": 2000,
"currentlyAvailable": 120,
"restoreRate": 100
}
}
}
}]
}You have 120 points, the query needs 500, and you recover at 100/sec. Wait (500 - 120) / 100 = 3.8 seconds and retry. No exponential backoff guesswork — Shopify literally hands you the math.
A Retry-Backoff Pattern That Actually Works
Here is a lean TypeScript retry helper that respects Shopify's own numbers:
async function shopifyFetch<T>(
query: string,
variables: Record<string, unknown> = {},
attempt = 0,
): Promise<T> {
const res = await fetch(`https://${shop}/admin/api/2026-01/graphql.json`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Shopify-Access-Token": token,
},
body: JSON.stringify({ query, variables }),
});
const json = await res.json();
const throttled = json.errors?.some(
(e: { extensions?: { code?: string } }) => e.extensions?.code === "THROTTLED",
);
if (throttled && attempt < 5) {
const cost = json.errors[0].extensions.cost;
const deficit = cost.requestedQueryCost - cost.throttleStatus.currentlyAvailable;
const waitMs = Math.ceil((deficit / cost.throttleStatus.restoreRate) * 1000) + 100;
await new Promise((r) => setTimeout(r, waitMs));
return shopifyFetch<T>(query, variables, attempt + 1);
}
if (json.errors) throw new Error(JSON.stringify(json.errors));
return json.data as T;
}This beats blind exponential backoff because it uses the server's own restore rate instead of guessing. Kirill Platonov's four strategies for Shopify API rate limits goes deeper on queueing patterns if you need a global rate limiter across workers.
Bulk Operations: For When You Are Syncing Everything

If your app ever needs to read every product, every order, or every customer, stop writing pagination loops. Use bulk operations. Bulk operations are Shopify's escape hatch for jobs that would otherwise drain any bucket in minutes.
How Bulk Operations Work
You submit a query through bulkOperationRunQuery, Shopify runs it asynchronously on their infrastructure, and when it finishes you download a JSONL file with the full result. The mutation itself costs 10 points. Polling costs nothing. The actual scan is free from your bucket's perspective.
mutation {
bulkOperationRunQuery(
query: """
{
products {
edges {
node {
id
title
variants {
edges { node { id sku inventoryQuantity } }
}
}
}
}
}
"""
) {
bulkOperation { id status }
userErrors { field message }
}
}You poll currentBulkOperation — or in API version 2026-01 and later, the new bulkOperations query that lets you list, filter, and paginate jobs — until status is COMPLETED, then fetch the url field and stream the JSONL file. The official bulk operations docs have the full state machine.
Bulk Mutations for Large Writes
For writes, bulkOperationRunMutation takes a JSONL file of inputs and runs a mutation against each row. In API versions 2026-01 and later, you can run up to five bulk mutation operations concurrently per shop — a big jump from the previous one-at-a-time limit. Each bulk mutation must finish within 24 hours, the JSONL file can't exceed 100 MB, and the mutation itself can only include one connection field.
Use bulk operations when you are syncing catalogs, importing historical orders, backfilling customer metafields, or doing anything that touches more than a few hundred records. A single bulk query can pull a 500,000-product catalog without ever hitting your rate limit, because the work runs outside your bucket. If you are designing the data layer for a larger integration, the patterns in our writeup on Shopify ERP integration lean heavily on bulk operations for exactly this reason.
Designing Queries That Don't Get Throttled
Rate limit safety is mostly a query design problem. Most throttling is self-inflicted, driven by one of four patterns.
Over-Requesting Pagination
Asking for first: 250 when you only need 20 records inflates the requested cost by 10x. Shopify refunds the difference, but only after the query runs. If your bucket was already low, the requested cost alone can push you into throttling. Always right-size first to what you actually need.
Deeply Nested Connections
Every level of nested connection multiplies cost. A query with products → variants → inventoryLevels at first: 50 each is evaluated at 50 × 50 × 50 = 125,000 items in the worst case — far above the 1,000-point ceiling. Split this into multiple queries or use a bulk operation.
Asking for Fields You Do Not Use
The cost model bills object fields at 1 point each and connection fields at 2 points plus items returned. Every customer, shippingAddress, or fulfillmentOrder you select that the UI never renders is pure overhead. Audit your queries quarterly and trim fields no component uses.
Firing Queries Without Checking Available Capacity
A well-behaved app inspects extensions.cost.throttleStatus.currentlyAvailable and slows down when capacity drops below a safety threshold (say, 20% of maximum). You do not need to wait for a THROTTLED error to act — you can pre-empt it.
The Shopify Partners blog on API rate limits and working with GraphQL has good diagrams of how the bucket drains under different query patterns.
Extensions, Background Jobs, and Webhook Subscriptions

Not every Shopify workload lives inside a user-initiated API request. Three other surfaces have their own rate limit behavior and are worth understanding.
Shopify Functions and Checkout UI Extensions
Checkout and admin UI extensions run inside Shopify's own runtime, so they do not count against your app's GraphQL bucket — but they have their own execution budgets. A Shopify Function has a 5-millisecond execution limit and a memory cap. You cannot make outbound network calls from inside a function, so all data has to come from the input query. If you are migrating from Scripts, the patterns in our Scripts to Functions migration guide cover the runtime constraints in detail.
Webhooks as a Rate Limit Offset
Webhooks are the best way to avoid polling. Subscribing to orders/create, products/update, or inventory_levels/update lets Shopify push state changes to you instead of you asking every 30 seconds. Every webhook delivery is a cost your bucket never pays. For any app doing sync, webhooks should be the primary integration pattern and polling should be the fallback.
Background Jobs and Queueing
If you have multiple workers pulling from the same store, treat the rate limit bucket as shared state. A Redis-based queue that tracks currentlyAvailable across workers prevents the classic thundering herd where every worker drains the bucket at the same moment. Pick one worker per store to own rate-limit accounting, or use a distributed semaphore keyed on the shop domain.
Shopify CLI and Claude Code Patterns for Safer Development
Two developer tools have changed how 2026 teams ship against the Admin API without tripping rate limits: the Shopify CLI and Claude Code (or any AI coding assistant).
Using the Shopify CLI to Profile Queries Before You Ship
The Shopify CLI ships with shopify app dev and a GraphiQL instance pre-authenticated to your dev store. Every query you run there shows the full extensions.cost block in the response. Before any query goes to production, run it in GraphiQL with realistic data and check three numbers:
- requestedQueryCost — is it under 1,000?
- actualQueryCost — is it close to requested, or are you massively over-paginating?
- currentlyAvailable after — would 10 parallel copies of this query drain the bucket?
If any answer is "no," rewrite before shipping. This five-minute check prevents most production throttling incidents.
Claude Code Patterns for Rate-Limit-Safe Code
When you ask an AI assistant to write Admin API code, the default output often skips cost awareness. Three prompt patterns keep it honest:
- "Use Admin API version 2026-01 and include extensions.cost handling in the client" — forces the generated wrapper to parse cost metadata.
- "Prefer bulk operations over pagination loops for queries that return more than 250 records" — biases the model toward the pattern that does not drain your bucket.
- "Retry THROTTLED errors using the restoreRate from extensions.cost, not exponential backoff" — produces the server-aware retry pattern from earlier in this guide.
If you work with a broader Shopify developer toolchain, the apps and integrations ecosystem category on our blog covers deeper patterns around auth, webhooks, and session management that pair naturally with rate limit design.
Monitoring Production: What to Log and Alert On

Once your app is live across dozens or hundreds of stores, per-request debugging stops scaling. You need telemetry. At minimum, log these four values for every Admin API call:
- Shop domain — to isolate noisy stores
- Operation name — to group by which query is driving cost
- requestedQueryCost and actualQueryCost — to spot over-paginating queries
- currentlyAvailable after — to see which stores are running hot
Alerting thresholds that have worked well in production:
| Signal | Threshold | Why |
|---|---|---|
| THROTTLED rate per shop | > 1% of calls | Indicates bucket starvation |
| Avg requested cost per op | > 500 points | Over-paginating or over-fetching |
| p95 currentlyAvailable | < 20% of maximum | Bucket is close to empty |
| Bulk ops failed | > 0 per day | JSONL too large or mutation broken |
If you feed these into a dashboard — Datadog, Honeycomb, or a simple Postgres table queried by Grafana — you will catch rate limit regressions before merchants complain. The engineering team at theecommerce.dev wrote a solid Shopify API survival guide covering production observability patterns for this kind of telemetry, and Murtaza Raheem's 2026 GraphQL cost optimization update has additional examples of alerting thresholds that surface real problems.
Common Mistakes That Burn Through Your Bucket
After reviewing a lot of Shopify apps, the same handful of mistakes show up again and again.
| Mistake | Why it hurts | Fix |
|---|---|---|
| Ignoring extensions.cost in responses | You fly blind until a 200 Throttled hits | Parse cost on every response; log it |
| Checking HTTP status for throttling | Shopify returns 200 with a GraphQL error, not 429 | Inspect the errors array for code THROTTLED |
| Using exponential backoff with random jitter | You wait longer than Shopify says you need to | Compute wait time from restoreRate and deficit |
| Using first: 250 everywhere "just in case" | Inflates requested cost 10x for no reason | Size first to the actual need |
| Polling loops instead of webhooks | Every poll drains the bucket; webhooks are free | Subscribe to relevant webhook topics |
| Running bulk ops sequentially when they could parallelize | Wastes time on API version 2026-01's 5-concurrent limit | Queue up to 5 bulk mutations per shop |
| Hardcoding bucket sizes from the docs | Plans change, customer upgrades happen | Read maximumAvailable from the response |
| Using deprecated currentBulkOperation on 2026-01+ | Only lets you see one operation at a time | Migrate to the new bulkOperations query |
| Sharing one global rate limit across workers | Thundering herd drains the bucket instantly | Per-shop semaphores or distributed queue |
| Not distinguishing public apps vs custom apps in limits | Both use GraphQL cost system now, but legacy REST limits differ | Check the API reference for each surface |
If any of these sound familiar, they are the first places to look when merchants report slow syncs or silent data misses.
Putting It All Together: A Rate-Limit-Safe Architecture

For an app that syncs large amounts of data between Shopify and a third-party system, the architecture that consistently survives 2026 rate limits looks like this:
- Webhooks first — subscribe to every change event you care about; poll only as a fallback
- Bulk operations for historical data — initial imports, backfills, and periodic reconciliation use bulk queries and mutations
- GraphQL client with cost awareness — every outbound request parses
extensions.costand updates a per-shop capacity tracker - Per-shop queue with backpressure — writes get queued per shop, with workers checking remaining capacity before firing mutations
- Server-math retry — THROTTLED responses compute wait time from
restoreRate, not from a guessed exponential curve - Observability on cost metrics — dashboards track requested cost, actual cost, and throttle rates per shop and per operation
This pattern works on Basic merchants and scales directly to Plus merchants without code changes — the only thing that shifts is the bucket size the limiter reports.
If you are building a Shopify app from scratch and want the broader architecture to sit alongside this rate limit design, our guide to creating a Shopify app from scratch covers the full lifecycle.
If you take only one thing from this entire guide, make it this: parse `extensions.cost` on every single response and log it. Every other pattern above — the retry helper, the bucket tracking, the query budgeting, the bulk operation decisions — depends on having visibility into the numbers Shopify is already sending you. The developers who never get paged about throttling are not the ones writing the cleverest queries. They are the ones who treat rate limit telemetry as a first-class concern from day one. Instrument first, optimize second, scale third.
What is the gnarliest rate limit bug you have shipped against in production? Drop into the Talk Shop community Discord where rate limit war stories get swapped constantly — someone has almost certainly hit the same wall.

About Talk Shop
The Talk Shop team — insights from our community of Shopify developers, merchants, and experts.
