A Shopify API version upgrade means changing one date string, then proving nothing broke. Shopify releases a new version every three months, on the first day of the quarter, and supports each stable version for at least 12 months. Pin the version explicitly, test against the release candidate, then ship the bump before the version you are on falls out of support.
The failure mode here is rarely an error. It is an app that keeps returning 200s while reading a field that no longer means what it used to.
What does pinning an API version actually do?
Every Admin GraphQL request carries the version in its path, /admin/api/2026-07/graphql.json. Shopify’s versioning documentation puts each release at 5pm UTC on the first day of the quarter, named after that quarter: 2026-07, 2026-10, 2027-01. Responses come back with an X-Shopify-API-Version header naming the version that actually ran the request, which is not always the version you asked for.
const API_VERSION = '2026-07';
export async function adminGraphql<T>(
shop: string,
accessToken: string,
query: string,
variables?: Record<string, unknown>,
): Promise<T> {
const response = await fetch(`https://${shop}/admin/api/${API_VERSION}/graphql.json`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': accessToken,
},
body: JSON.stringify({ query, variables }),
});
const served = response.headers.get('x-shopify-api-version');
if (served !== API_VERSION) {
console.warn(`shopify api fell forward: asked for ${API_VERSION}, served ${served}`);
}
const deprecated = response.headers.get('x-shopify-api-deprecated-reason');
if (deprecated) {
console.warn(`shopify deprecated call: ${deprecated}`);
}
const body = await response.json();
if (body.errors) {
throw new Error(JSON.stringify(body.errors));
}
return body.data as T;
}
There is a second pin, and it lives somewhere else. Webhook payloads are versioned the same way as API responses, but in an app built with the Shopify CLI that version sits in shopify.app.toml, the same file that declares the access scopes your app asks for:
[webhooks]
api_version = "2026-07"
[[webhooks.subscriptions]]
topics = [ "orders/create" ]
uri = "https://example.co.uk/webhooks/orders"
The app configuration reference lists api_version as a required string in [webhooks], and a second one in [events] that governs the version your subscription queries are validated and run against. Bump the constant in your client code and leave those strings alone, and you have an app reading one version’s payload shape in the handler and another version’s schema in the loader. Edit them together, and remember the file has to be deployed before a store sees the change; committing it does nothing on its own. Our walkthrough of webhook HMAC verification covers what else that handler owes you.
What happens if you never upgrade?
Nothing, for a while. Then Shopify falls forward: if your app targets a version it can no longer access, the request is not rejected. It is answered using the oldest accessible stable version. The integration keeps working, on a version nobody on your team chose or tested, and the header in the client above is the only place that difference surfaces.
The other signal is the API health report, under Monitoring in the app’s dashboard. It lists the deprecated calls the app has made in the past 14 days and gives the app one of three statuses. OK means no deprecated calls in that window. Fix by carries a date, with a yellow indicator when you have between 30 days and nine months to act and red when you have less than 30. Fix overdue means the app is calling unsupported versions. Shopify’s wording on what follows is plain: you get a notice on the report and an email if an app in that state is at risk of being delisted or having installs blocked.
Fourteen days is the number that catches teams out. A nightly stock sync shows up there. A quarterly finance export that ran on the first of the month does not, if you look on the twentieth.
How do you find out what breaks before you upgrade?
Read the release notes for the version you are moving to, and accept that changes arrive in three different shapes.
Removals are the loud ones. The 2026-10 release notes drop the deprecated priceRule field from DraftOrderDiscountNotAppliedWarning, remove Customer.lastIncompleteCheckout and the unreachable Checkout type from the Customer Account API, and take ITEM_NOT_STOCKED_AT_LOCATION out of InventoryAdjustQuantities, InventoryMoveQuantities and InventorySetOnHandQuantities. Query a field that is gone and you get an error on the first request, which is the outcome you want.
Additions are quieter. In 2026-10, Order.displayFulfillmentStatus can return FULFILLMENT_NOT_REQUIRED for orders with zero remaining fulfillable quantity, where it previously returned UNFULFILLED. Nothing you wrote stops compiling. Your exhaustive switch just falls through to its default branch, and an order shows the wrong badge in an admin view until somebody reports it.
The third shape is a call that used to be tolerated and now is not. Also in 2026-10, the GraphQL Admin API returns an error when a query filters by a metafield that is not valid for filtering, instead of ignoring the predicate. Nothing in your code changed, and the call now fails.
For the removals, introspect the target version and check the names against your own operation files:
curl -s -X POST "https://$SHOP/admin/api/2026-10/graphql.json" \
-H "X-Shopify-Access-Token: $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"query":"{ __schema { types { name fields(includeDeprecated: true) { name isDeprecated deprecationReason } } } }"}' \
| jq -r '.data.__schema.types[] | select(.fields) | .name as $t | .fields[]
| select(.isDeprecated) | "\($t).\(.name)\t\(.deprecationReason)"' \
> deprecated-2026-10.tsv
That response is big, so write it to a file and work from there. Anything deprecated in the release candidate is a candidate for removal in a later quarter, so cut the names out of that file and grep -F -f them against your .graphql operations. That gives you the list before the schema forces the issue.
On REST the signal comes to you unprompted. Calls that include deprecated behaviour return an X-Shopify-API-Deprecated-Reason response header carrying a link to the relevant release note, per the REST versioning documentation. Log it. Bear in mind that REST has been a legacy API since 1 October 2024, and that new public apps submitted to the App Store have had to be GraphQL-only since 1 April 2025. A REST deprecation may be telling you to do the migration, not the version bump.
When should you run the Shopify API version upgrade?
On the first day of the quarter, in a branch. The release candidate for the next version appears the same day the current one goes stable. When 2026-04 became stable on 1 April 2026, 2026-07 was already published as a release candidate, so there are three months to test in. As of September 2026, 2026-07 is stable and 2026-10 is the release candidate until it becomes stable on 1 October 2026.
The support window gives you room beyond that. Each stable version lasts a minimum of 12 months, with at least nine months of overlap between consecutive versions. So the hard deadline is never less than a year away. What you are choosing is batch size. Upgrade quarterly and each bump covers one quarter of changes, against code somebody on the team still remembers writing. Leave it until the health report turns red and you are reading four sets of release notes at once, on a clock.
The routine itself is short. Bump the constant and both TOML keys on a branch, point your integration tests at a development store, and run them against the release candidate. Read the release notes for every object your code touches, then deploy on or soon after the stable date. One assertion in CI that fails the build when the served version differs from the pinned one closes the loop, because fall-forward is otherwise invisible.
Put the bump in the calendar for the first working day of each quarter. Keep the version in exactly one constant plus the app config, so the diff is three lines and the review argues about release notes instead of hunting for stragglers. What I would not do is run the release candidate in production to get ahead: it can carry backwards-incompatible changes right up to the stable date, which is the one guarantee it does not make. If you have inherited an app already on Fix overdue, start with the health report filtered to the last day, fix what it lists, and only then bump forward one version at a time. Skipping straight to the newest version bundles four quarters of breakage into one deploy, and you will not know which change caused the failure.
Whoooop builds and maintains Shopify apps and storefronts, including the unglamorous quarterly work of keeping them on a supported API version. If an integration of yours is on a version nobody has touched in two years, our Shopify development page explains how we usually pick that up.