What the Shopify Admin API Does and Why It Matters
The Shopify Admin API is the programmatic backbone of every Shopify store. It gives developers direct access to manage products, orders, customers, inventory, fulfillment, and virtually every resource that a merchant interacts with through the Shopify admin dashboard. If the Storefront API is the customer-facing front door, the Shopify Admin API is the operations center.
Over 5.6 million live Shopify stores depend on this API — whether through the apps they install, the custom integrations their teams build, or the automation workflows that keep their businesses running. Every time an app syncs inventory to a warehouse, generates a shipping label, or sends a personalized email after purchase, the Admin API is the engine behind it.
This guide covers everything you need to build production-ready integrations: authentication flows, the GraphQL vs REST decision, core resources, rate limiting strategies, pagination patterns, webhooks, and common use cases with code you can adapt. If you are exploring the broader Shopify development ecosystem, this is the API you will spend most of your time with.
Authentication: Getting Access to Store Data
Every Shopify Admin API request requires a valid access token. How you obtain that token depends on whether you are building a public app (distributed through the Shopify App Store) or a custom app (built for a single store).
OAuth 2.0 for Public and Custom Apps
As of 2026, both public and custom apps use OAuth 2.0 for authentication. Shopify discontinued the creation of legacy custom apps directly from the admin — all new apps must be created through the Shopify Developer Dashboard with an OAuth flow.
The OAuth process follows these steps:
- Redirect the merchant to Shopify's authorization URL with your app's requested scopes
- Merchant approves the permissions your app needs
- Shopify redirects back to your app with a temporary authorization code
- Exchange the code for a permanent access token via a server-side POST request
Here is the token exchange step in Node.js:
const response = await fetch(
`https://${shop}/admin/oauth/access_token`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: SHOPIFY_API_KEY,
client_secret: SHOPIFY_API_SECRET,
code: authorizationCode,
}),
}
);
const { access_token, scope } = await response.json();Access Scopes
Access scopes define what resources your app can read or write. You request them during the OAuth flow, and the merchant must approve them explicitly. Some commonly used scopes:
| Scope | Access |
|---|---|
| read_products / write_products | Product catalog, variants, images |
| read_orders / write_orders | Order data, fulfillments, refunds |
| read_customers / write_customers | Customer records, addresses |
| read_inventory / write_inventory | Inventory levels, locations |
| read_shipping / write_shipping | Shipping rates, carrier services |
Best practice: Request the minimum scopes your app needs. Merchants are more likely to install apps that ask for less access, and Shopify's review team will flag unnecessarily broad scope requests during App Store submission.
Token Exchange (Session Tokens)
For embedded apps, Shopify recommends the token exchange flow instead of the traditional authorization code grant. Your app receives a session token from App Bridge, exchanges it server-side for an access token, and avoids redirect-based OAuth entirely. The Remix app template handles this automatically.
GraphQL vs REST: Which API to Use

Shopify offers two Admin API surfaces — GraphQL and REST — but the direction is clear. The REST Admin API became a legacy API on October 1, 2024, and as of April 2025, all new public apps submitted to the Shopify App Store must use GraphQL exclusively.
Why GraphQL Is the Future
| Factor | GraphQL | REST (Legacy) |
|---|---|---|
| Data fetching | Request exactly the fields you need | Fixed response shapes, often over-fetching |
| Relationships | Traverse connections in a single query | Multiple round-trip requests |
| Rate limiting | Cost-based (more efficient) | Request-count-based (40/second) |
| New features | All new resources ship here first | No new features since deprecation |
| Bulk operations | Native support for large data exports | Not available |
| App Store requirement | Required for new public apps | Not accepted for new submissions |
When REST Still Makes Sense
Existing custom apps that use REST endpoints for basic operations can continue running — Shopify has not announced a hard shutdown date. If you have a stable integration doing simple CRUD operations and do not need new features, migrating is not urgent. But for any new development, GraphQL is the only supported path forward.
Your First GraphQL Query
Every Shopify Admin API GraphQL request goes to a single endpoint:
POST https://{store}.myshopify.com/admin/api/2026-01/graphql.jsonInclude your access token as a header:
const response = await fetch(
`https://${shop}/admin/api/2026-01/graphql.json`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Shopify-Access-Token": accessToken,
},
body: JSON.stringify({
query: `{
shop {
name
primaryDomain { url }
plan { displayName }
}
}`,
}),
}
);
const { data } = await response.json();
console.log(data.shop.name); // "My Store"Core Resources: Products, Orders, Customers, and Inventory
The Shopify Admin API exposes dozens of resource types, but four account for the majority of integration work. Understanding their data models and relationships is essential for productive development.
Products and Variants
Products are the most queried resource in the Admin API. Each product contains variants (size/color combinations), images, metafields, and collection associations.
query GetProducts {
products(first: 10) {
edges {
node {
id
title
status
totalVariants
variants(first: 5) {
edges {
node {
id
title
price
inventoryQuantity
sku
}
}
}
images(first: 1) {
edges {
node {
url
altText
}
}
}
}
}
}
}Key detail: Shopify supports up to 2,000 variants per product (up from 100 in earlier API versions). This change is only available through the GraphQL API — another reason to move off REST.
Orders
Orders include line items, shipping details, payment status, fulfillment records, and refund history. A single order query can pull all of this in one request:
query GetOrder($id: ID!) {
order(id: $id) {
name
displayFinancialStatus
displayFulfillmentStatus
totalPriceSet { shopMoney { amount currencyCode } }
lineItems(first: 50) {
edges {
node {
title
quantity
variant { sku }
originalUnitPriceSet { shopMoney { amount } }
}
}
}
shippingAddress {
city
provinceCode
countryCodeV2
}
}
}Customers
Customer records store contact information, order history, tags for segmentation, and metafields for custom data. The customers connection supports filtering by email, tag, or creation date.
Inventory
Inventory management in Shopify is location-aware. Each variant tracks inventory levels independently across multiple locations (warehouses, retail stores, third-party fulfillment centers). Use the inventoryAdjustQuantities mutation to update stock levels:
mutation AdjustInventory($input: InventoryAdjustQuantitiesInput!) {
inventoryAdjustQuantities(input: $input) {
inventoryAdjustmentGroup {
reason
changes {
name
delta
}
}
userErrors { field message }
}
}Rate Limiting: How to Stay Within Bounds

Rate limiting is where most developers first run into trouble with the Shopify Admin API. The GraphQL and REST APIs use fundamentally different throttling models, and understanding the GraphQL model is critical for building reliable integrations.
GraphQL: Calculated Query Cost
The GraphQL Admin API uses a calculated query cost system based on a leaky bucket algorithm. Instead of counting requests, Shopify assigns a point cost to each query based on the fields and connections you request.
| Plan Tier | Bucket Size | Restore Rate |
|---|---|---|
| Standard | 1,000 points | 50 points/second |
| Advanced | 2,000 points | 100 points/second |
| Shopify Plus | 10,000 points | 500 points/second |
Every GraphQL response includes a cost extension showing both the requested cost (estimated before execution) and the actual cost (calculated during execution). If the actual cost is lower than requested, the difference is refunded to your bucket:
{
"extensions": {
"cost": {
"requestedQueryCost": 42,
"actualQueryCost": 12,
"throttleStatus": {
"maximumAvailable": 1000,
"currentlyAvailable": 988,
"restoreRate": 50
}
}
}
}Pro tip: Add the Shopify-GraphQL-Cost-Debug: 1 header to any request to see a field-by-field breakdown of where your query cost comes from. This is invaluable for optimizing expensive queries.
REST: Request-Based Throttling
The legacy REST API uses a simpler model — 40 requests per second per app per store, with a bucket size of 80. Each request costs one unit regardless of complexity.
Handling Throttle Errors
When you exceed your rate limit, Shopify returns a THROTTLED error in GraphQL or a 429 Too Many Requests in REST. Implement exponential backoff:
async function shopifyGraphQL(query: string, variables?: Record<string, unknown>) {
let retries = 0;
const maxRetries = 3;
while (retries < maxRetries) {
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Shopify-Access-Token": accessToken,
},
body: JSON.stringify({ query, variables }),
});
const json = await response.json();
if (json.errors?.[0]?.extensions?.code === "THROTTLED") {
const waitTime = Math.pow(2, retries) * 1000;
await new Promise((resolve) => setTimeout(resolve, waitTime));
retries++;
continue;
}
return json;
}
throw new Error("Max retries exceeded for Shopify API");
}Pagination: Fetching Large Datasets
Shopify's Admin API uses cursor-based pagination exclusively. Every connection (products, orders, customers) returns a pageInfo object with cursors for navigating through results.
Forward Pagination Pattern
query GetProducts($cursor: String) {
products(first: 50, after: $cursor) {
edges {
node {
id
title
}
}
pageInfo {
hasNextPage
endCursor
}
}
}Loop through pages by passing endCursor as the $cursor variable until hasNextPage returns false. Each page request costs points against your rate limit bucket, so factor pagination into your throttle strategy.
Bulk Operations for Large Exports
When you need to export thousands (or millions) of records, individual paginated queries become impractical. Shopify's bulk operations solve this by running your query asynchronously on Shopify's infrastructure and providing a downloadable JSONL file when complete.
mutation BulkExportProducts {
bulkOperationRunQuery(
query: """
{
products {
edges {
node {
id
title
variants {
edges {
node {
sku
inventoryQuantity
}
}
}
}
}
}
}
"""
) {
bulkOperation {
id
status
}
userErrors { field message }
}
}Poll the operation status, and when it completes, download the results from the provided URL. As of API version 2026-01, apps can run up to five concurrent bulk query operations per store — a significant improvement from the previous limit of one.
| Method | Best For | Rate Limit Impact |
|---|---|---|
| Paginated queries | Small datasets (< 1,000 records) | Each page costs query points |
| Bulk operations | Large exports (1,000+ records) | Minimal — runs asynchronously |
Webhooks: Real-Time Event Notifications

Instead of polling the Admin API for changes, webhooks push event notifications to your app in real time. Whenever a product is updated, an order is placed, or a customer record changes, Shopify sends an HTTP POST to your registered endpoint.
Subscribing to Webhooks via GraphQL
Use the webhookSubscriptionCreate mutation to register a webhook:
mutation CreateWebhook {
webhookSubscriptionCreate(
topic: ORDERS_CREATE
webhookSubscription: {
callbackUrl: "https://your-app.com/webhooks/orders"
format: JSON
}
) {
webhookSubscription {
id
topic
endpoint {
... on WebhookHttpEndpoint {
callbackUrl
}
}
}
userErrors { field message }
}
}Common Webhook Topics
| Topic | Fires When |
|---|---|
| ORDERS_CREATE | A new order is placed |
| ORDERS_UPDATED | Any order field changes |
| ORDERS_PAID | Payment is captured |
| PRODUCTS_UPDATE | A product or variant is modified |
| PRODUCTS_DELETE | A product is removed |
| INVENTORY_LEVELS_UPDATE | Stock levels change at any location |
| CUSTOMERS_CREATE | A new customer account is created |
| APP_UNINSTALLED | A merchant removes your app |
Verifying Webhook Authenticity
Every webhook request includes an X-Shopify-Hmac-Sha256 header. Always verify this signature before processing the payload — otherwise your endpoint is vulnerable to spoofed requests:
import crypto from "crypto";
function verifyWebhook(body: string, hmacHeader: string): boolean {
const hash = crypto
.createHmac("sha256", SHOPIFY_API_SECRET)
.update(body, "utf8")
.digest("base64");
return crypto.timingSafeEqual(
Buffer.from(hash),
Buffer.from(hmacHeader)
);
}Webhook Delivery and Retries
Shopify expects your endpoint to respond with a 2xx status within 5 seconds. If it does not, Shopify retries delivery up to 19 times over 48 hours with increasing intervals. Design your webhook handler to acknowledge receipt immediately and process the payload asynchronously — never do heavy computation inside the response cycle.
Common Use Cases and Integration Patterns
The Shopify Admin API powers a wide range of integrations. Here are the patterns that cover the majority of real-world use cases, and that you will likely build variations of in your own app development work.
Inventory Sync
Syncing inventory between Shopify and an external system (ERP, warehouse management, POS) is one of the most common Admin API integrations. The pattern involves:
- Initial sync — bulk export all inventory levels using a bulk operation
- Ongoing sync — subscribe to
INVENTORY_LEVELS_UPDATEwebhooks for real-time changes - Write-back — use
inventoryAdjustQuantitiesto push external changes back to Shopify
Order Fulfillment Automation
When an order is placed, your integration can automatically create fulfillment records, generate shipping labels, and notify the customer:
mutation FulfillOrder($fulfillment: FulfillmentInput!) {
fulfillmentCreate(fulfillment: $fulfillment) {
fulfillment {
id
status
trackingInfo {
number
url
}
}
userErrors { field message }
}
}Custom Reporting and Analytics
The Admin API provides access to raw order, product, and customer data that you can aggregate into custom reports beyond what the Shopify admin offers. Combine orders queries with date filters to build revenue breakdowns, product performance reports, or customer lifetime value calculations. For large stores, use bulk operations to export the data to your own analytics infrastructure.
Common Mistakes and What to Avoid
Building with the Shopify Admin API is straightforward once you understand the patterns, but these mistakes trip up developers consistently.
Over-Fetching Data
GraphQL lets you request exactly the fields you need — but many developers copy example queries without trimming unused fields. Every additional field adds to your query cost. Audit your queries and remove any field you are not actively using.
Ignoring Webhook Verification
Skipping the HMAC signature check on incoming webhooks is a security vulnerability. Attackers can send fake payloads to your endpoint to trigger unintended actions. Always verify the X-Shopify-Hmac-Sha256 header.
Not Handling Pagination
If your integration queries products or orders without paginating, you will only get the first page of results and silently miss the rest. Always check pageInfo.hasNextPage and loop until all pages are consumed.
Hardcoding API Versions
Shopify releases new API versions quarterly and deprecates old ones after a year. Hardcoding a version like 2024-04 means your integration will eventually break. Store the API version in a configuration variable and update it during your regular maintenance cycle.
Blocking on Webhook Processing
Doing heavy computation inside your webhook handler (database writes, external API calls, file processing) causes timeouts and failed deliveries. Accept the webhook with a 200 response immediately, queue the payload, and process it asynchronously.
| Mistake | Consequence | Fix |
|---|---|---|
| Over-fetching fields | Faster rate limit exhaustion | Trim queries to needed fields only |
| Skipping HMAC check | Security vulnerability | Verify every webhook signature |
| No pagination handling | Missing data | Always loop through pageInfo |
| Hardcoded API version | Breaking changes on deprecation | Use a configurable version string |
| Blocking webhook handler | Missed deliveries, retries | Acknowledge immediately, process async |
GraphQL Mutations: Writing Data Back to Shopify

Reading data is only half the story. Most integrations also need to create, update, or delete resources. Mutations in the Shopify Admin API follow a consistent pattern.
Creating a Product
mutation CreateProduct($product: ProductCreateInput!) {
productCreate(product: $product) {
product {
id
title
handle
status
}
userErrors {
field
message
}
}
}Variables:
{
"product": {
"title": "Classic Cotton Tee",
"productType": "T-Shirts",
"status": "DRAFT",
"variants": [
{
"price": "29.99",
"sku": "CCT-BLK-M",
"inventoryQuantities": [
{
"locationId": "gid://shopify/Location/12345",
"name": "available",
"quantity": 100
}
]
}
]
}
}Updating Existing Resources
Every mutation that modifies an existing resource requires the resource's global ID (the gid://shopify/... format). Always check the userErrors array in the response — a successful HTTP response does not mean the mutation succeeded:
const { data } = await shopifyGraphQL(mutation, variables);
if (data.productUpdate.userErrors.length > 0) {
console.error("Mutation failed:", data.productUpdate.userErrors);
// Handle validation errors — missing required fields, invalid values, etc.
}Building Your Integration: A Step-by-Step Approach
Whether you are building a standalone app or a custom integration for a single store, the development workflow follows the same sequence. If you want a deeper walkthrough of the scaffolding process, our guide on how to create a Shopify app from scratch covers the full setup.
Step 1: Define Your Scope
Before writing code, list the exact resources your integration needs to read and write. Map each to the corresponding access scope. This determines your OAuth configuration and what the merchant sees during installation.
Step 2: Set Up Authentication
Use the Shopify CLI and the official Remix template for the fastest path to a working OAuth flow. For custom integrations that do not need a full app framework, implement the authorization code grant manually.
Step 3: Build and Test Queries
Use Shopify's built-in GraphiQL explorer to prototype your queries against a development store before writing application code. This lets you validate field availability, test filters, and check query costs interactively.
Step 4: Implement Webhook Handlers
Register webhooks for the events your integration depends on. Build your handlers with immediate acknowledgment and async processing from day one — retrofitting this later is painful.
Step 5: Handle Rate Limits and Errors
Implement the exponential backoff pattern from the rate limiting section above. Log your query costs and throttle status so you can monitor usage in production.
Step 6: Test with Real Data
Shopify development stores have limitations (no real payment processing, limited data volume). Before launching, test against a store with realistic data volume to catch pagination edge cases and rate limit issues.
Admin API vs Storefront API: When to Use Each

Understanding the boundary between the Admin API and the Storefront API is essential for architecting your integration correctly.
| Characteristic | Admin API | Storefront API |
|---|---|---|
| Purpose | Backend store management | Customer-facing experiences |
| Authentication | OAuth access token (server-side) | Storefront access token (can be public) |
| Rate limits | Cost-based, 50 pts/sec (standard) | No rate limits on reads |
| Access | Full CRUD on all resources | Read products, manage carts, initiate checkout |
| Typical consumers | Apps, integrations, automation | Headless storefronts, mobile apps, kiosks |
| Sensitive data | Yes — orders, customers, financials | No — only public catalog data |
Use the Admin API when you need to manage store operations: creating products, fulfilling orders, adjusting inventory, managing customers, or reading financial data.
Use the Storefront API when you are building a customer-facing experience: displaying products, managing shopping carts, or routing customers to checkout. Our Storefront API getting started guide walks through the full setup.
Many production architectures use both APIs together. A headless storefront reads product data through the Storefront API for performance and then uses the Admin API server-side for order management, fulfillment, and reporting.
Frequently Asked Questions
Is the Shopify REST Admin API still available?
Yes. The REST Admin API was marked as legacy in October 2024, but existing integrations continue to function. However, no new features are being added to REST, and all new public apps must use GraphQL as of April 2025. Plan your migration to GraphQL proactively.
How do I test the Admin API without a live store?
Create a free development store through your Shopify Partner account. Development stores give you full Admin API access, unlimited test orders, and no subscription costs.
What happens when my app exceeds the rate limit?
Shopify returns a THROTTLED error in GraphQL or a 429 HTTP status in REST. Your bucket refills at a steady rate (50 points/second on standard plans), so implementing a short backoff is usually sufficient to recover.
Can I use the Admin API from a frontend application?
No. Admin API access tokens must be kept server-side — they grant access to sensitive store data (orders, customers, financials). Exposing an Admin API token in client-side code is a critical security vulnerability. Use the Storefront API for any client-side data needs.
How many API calls can I make per second?
For GraphQL, it depends on your query cost, not the number of calls. A simple query might cost 2 points while a complex one costs 500. On standard plans, you get 50 points restored per second with a 1,000-point bucket. For REST, the limit is 40 requests per second with a bucket size of 80.
Start Building with the Shopify Admin API
The Shopify Admin API is the most powerful tool in a Shopify developer's toolkit. It provides complete programmatic access to every aspect of store management — from product catalog operations to order fulfillment to customer data.
The path forward is clear: build with GraphQL, implement proper authentication with the minimum required scopes, handle rate limits gracefully, use webhooks instead of polling, and leverage bulk operations for large datasets. These fundamentals will serve you whether you are building a single custom integration or a public app for the Shopify App Store.
If you are ready to go deeper, explore our guides on Shopify app development and building an app from scratch. And if you have questions about your specific integration architecture, the Talk Shop community is where Shopify developers share solutions and help each other build.
What are you building with the Shopify Admin API? Drop into our community and share your project.

About Talk Shop
The Talk Shop team — insights from our community of Shopify developers, merchants, and experts.
Related Insights
The ecommerce newsletter that's actually useful.
Daily trends, teardowns, and tactics from the top 1% of ecommerce brands. Delivered every morning.
