Appearance
Admin GraphQL API
GraphQL only (for new apps)
Since 1 April 2025, new public apps submitted to the App Store must use the GraphQL Admin API. REST Admin is legacy. It still works for existing apps but gets no new features. Learn GraphQL; don't start with REST.
Versioning
- Endpoint:
https://{shop}.myshopify.com/admin/api/{version}/graphql.json - A new version ships every quarter, on the first day of the quarter:
2026-01,2026-04,2026-07,2026-10… - Each version is supported for at least 12 months.
2026-10is current stable, and2027-01is the release candidate. - Calling a retired version silently falls forward to the oldest supported one, which can break you. Bump your version every quarter or two.
Your first query
ts
const { admin } = await authenticate.admin(request);
const res = await admin.graphql(
`#graphql
query RecentOrders($first: Int!) {
orders(first: $first, sortKey: CREATED_AT, reverse: true) {
edges {
node {
id
name
createdAt
totalPriceSet { shopMoney { amount currencyCode } }
customer { displayName }
}
}
pageInfo { hasNextPage endCursor }
}
}`,
{ variables: { first: 10 } },
);
const { data } = await res.json();Mutations and userErrors
ts
const res = await admin.graphql(
`#graphql
mutation SetNote($input: OrderInput!) {
orderUpdate(input: $input) {
order { id note }
userErrors { field message }
}
}`,
{ variables: { input: { id: "gid://shopify/Order/123", note: "Sent WhatsApp confirmation" } } },
);Always check userErrors
A mutation that fails validation still returns HTTP 200. The failure is in userErrors. Not checking it is the #1 silent bug.
IDs are GIDs
Shopify IDs look like gid://shopify/Product/1234567890. Store the full GID. Webhook payloads often include both id (numeric) and admin_graphql_api_id (GID).
Pagination
Cursor-based: pass after: endCursor while hasNextPage is true. Page size is at most 250.
Rate limits: cost-based
Each query has a calculated cost. Your app has a bucket of points that refills every second, and the bucket is bigger on Plus. The response includes:
json
"extensions": { "cost": { "requestedQueryCost": 12, "actualQueryCost": 8,
"throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1992, "restoreRate": 100 } } }If you are throttled, you get a THROTTLED error. Back off and retry.
Bulk operations
For large exports, such as all orders for a year, use bulkOperationRunQuery. Shopify runs the query asynchronously and gives you a JSONL file URL. Subscribe to the bulk_operations/finish webhook rather than polling.
Metafields & metaobjects
- Metafields: custom fields on any resource (
namespace.key). Use the$appnamespace for app-owned data. - Metaobjects: your own custom content types, stored in Shopify.
These are often a better place for per-shop settings than your own database.
Tools
- GraphiQL:
shopify app devgives you a GraphiQL explorer (pressg) authenticated against your dev store. - Codegen: the template ships
@shopify/api-codegen-preset. Runnpm run graphql-codegenfor typed queries.
Project step: list the 10 most recent orders on your app's home page.
Check yourself
- A mutation returns 200 but nothing changed. Where do you look?
- How do you export 500k orders without hitting rate limits?