Migrate to expiring offline access tokens
Public apps must use expiring offline access tokens for GraphQL Admin API requests. New public apps already can't use non-expiring tokens, and existing public apps can't after January 1, 2027. After that date, Shopify rejects a GraphQL Admin API request that presents a non-expiring offline token, and merchants lose access to your app until they re-authenticate.
Shopify enforces this on GraphQL Admin API requests only. It doesn't apply to custom apps or apps created by merchants.
You need this page only if your public app still requests non-expiring offline access tokens for the GraphQL Admin API. Newly scaffolded apps already request expiring tokens through the expiringOfflineAccessTokens flag. This page covers how to stop requesting non-expiring tokens and migrate existing installations.
Anchor to What you need to doWhat you need to do
- Start requesting expiring tokens: Enable the app template flag, or include
expiring=1in your app's normal ID-token exchange or authorization code grant requests. - Migrate existing installations (for inactive users): Use the direct migration flow if you don't want to wait for users to interact with your app and trigger a new token acquisition.
Find the section below that matches how your app requests tokens today.
Anchor to Apps built from an app templateApps built from an app template
If your app uses @shopify/shopify-app-remix or @shopify/shopify-app-react-router, add the expiringOfflineAccessTokens flag to your shopifyApp() configuration and redeploy:
The app template handles everything else. New token exchanges issue expiring tokens, and the template stores and refreshes them automatically.
Anchor to Apps using token exchange directlyApps using token exchange directly
If your app implements token exchange itself, add expiring=1 to your request:
Store the access_token and refresh_token from the response, along with expires_in and refresh_token_expires_in so you know when each expires. Use the refresh token to get a new access token before expires_in seconds elapse, then store the refresh token that comes back and use that one next time. See Refresh an expiring offline token for the refresh flow.
If your app uses the OAuth authorization code grant, add expiring=1 to the request that exchanges the authorization code for an access token:
Store the access_token and refresh_token from the response, along with expires_in and refresh_token_expires_in. Use the refresh token before expires_in seconds elapse.
Anchor to Migrate existing tokens without a user sessionMigrate existing tokens without a user session
Use this migration flow if you don't want to wait for users to open your app and trigger a new token acquisition. It also covers background-only apps, such as webhook consumers, app proxy backends, and Flow actions.
This migration request uses the stored non-expiring token and your app's client credentials. It doesn't require an ID token or a user session.
Migration permanently invalidates the old token for GraphQL Admin API requests. Store the access token and refresh token returned by the exchange.
Migration permanently invalidates the old token for GraphQL Admin API requests. Store the access token and refresh token returned by the exchange.
The response has the same shape as any expiring offline token response. Store the access_token, refresh_token, expires_in, and refresh_token_expires_in, then discard the old token.
For the initial exchange, subject_token must be an active non-expiring offline access token that belongs to your app and store. Shopify rejects online tokens, delegate tokens, scope-restricted tokens, and tokens that already expire. Both requested_token_type and expiring=1 are required. When a parameter doesn't meet these rules, Shopify returns 400 Bad Request with {"error": "invalid_subject_token"} or {"error": "invalid_requested_token_type"}.
Anchor to Recover a lost migration responseRecover a lost migration response
To recover a lost response, repeat the same migration request with the original non-expiring token and valid client credentials within seven days of the initial exchange. Eligible retries return the same token pair. Shopify extends the access token's expiry when needed, but doesn't extend the refresh token's expiry. Save the returned pair and expiry values.
Recovery stops after your app refreshes the issued pair or a later token acquisition for the same store retires it. If a retry returns invalid_subject_token, then acquire a new token through ID-token exchange or the authorization code grant.
Anchor to Handle errors during the transitionHandle errors during the transition
Existing installations migrate when your app acquires and stores an expiring token pair, through normal token acquisition or the direct migration flow.
When Shopify rejects a GraphQL Admin API request that uses a non-expiring offline token, it returns 403 Forbidden. The response's errors value contains Non-expiring access tokens are no longer accepted for the Admin API, followed by a migration link, so match on that phrase rather than the whole string. A missing access scope also returns 403, so check the message before you re-authenticate.
Handle the rejection according to how your app authenticates:
- Apps built from an app template:
authenticate.admin(request)handles re-authentication automatically. - Apps using token exchange directly: catch the
403and re-run token exchange to get a fresh token. - Apps using the authorization code grant: catch the
403, clear the stored token, and redirect the merchant through the OAuth flow to get a new access token. - Refresh token errors: if a refresh token is expired, already replaced by one your app has since used, or otherwise invalid, Shopify returns
401 Unauthorizedwith{"error": "invalid_request"}. Treat it as a signal to re-authenticate: the merchant must reopen your app in the Shopify admin to trigger a new token request. For the full retry and error behavior, see Refresh an expiring offline token.
Anchor to Next stepsNext steps
- Learn how access tokens work, including the difference between offline and online access modes.
- Set up token refresh so expiring tokens renew before they lapse.
- Review how to rotate your client secret, which also requires replacing stored tokens.