About offline access tokens
When you create an API access token for the GraphQL Admin API, you can choose between two access modes: offline and online. Offline is the default access mode when none is specified. Tokens with offline access mode are meant for service-to-service requests where no user interaction is involved. Offline access mode is ideal for background work in response to webhooks, or for maintenance work in backgrounded jobs.
Anchor to Expiring vs non-expiring offline tokensExpiring vs non-expiring offline tokens
As of December 2025, Shopify supports expiring offline access tokens, providing enhanced security through token rotation with a refresh token while maintaining the ability for apps to perform background operations without user interaction.
Anchor to Expiring offline tokensExpiring offline tokens
Introduced in December 2025, expiring offline tokens provide enhanced security by allowing apps to regularly rotate access tokens using refresh tokens. Apps can continue performing background operations without user interaction while maintaining better security through token expiration and app-managed token refresh. Expiring offline tokens have the following characteristics:
- 90-day refresh token lifetime: A refresh token is provided in the response when obtaining new access tokens.
- Token refresh: Apps can refresh expired tokens without merchant intervention.
- Only one expiring offline token can be active per app/shop combination: Acquiring a new expiring token will revoke all previous expiring tokens for that shop.
Anchor to Non-expiring offline tokensNon-expiring offline tokens
Prior to December 2025, non-expiring offline tokens were the default and only option for offline access. These tokens grant permanent access to a shop's data and can only be revoked through app uninstallation or secret revocation, making them less secure than expiring tokens. Non-expiring offline tokens have the following characteristics:
- No expiration: Tokens remain valid indefinitely until app is uninstalled or secret revocation.
- Acquiring offline tokens: Getting offline tokens for the same shop and installation returns the same access token each time.
Apps can migrate from non-expiring to expiring tokens using the token exchange grant. This is a one-time, irreversible migration per shop.
Anchor to Acquiring expiring offline tokensAcquiring expiring offline tokens
Expiring offline tokens are supported in the different token acquisition flows.
Anchor to Token exchange from session tokenToken exchange from session token
When using token exchange, include the expiring=1 parameter:
| Parameter | Description |
|---|---|
client_idrequired | The API key for the app. |
client_secretrequired | The client secret for the app. |
grant_typerequired | The value urn:ietf:params:oauth:grant-type:token-exchange indicates that token exchange is to be performed. |
subject_tokenrequired | An ID token that represents the identity and active browser session of a merchant using the app. |
subject_token_typerequired | The value urn:ietf:params:oauth:token-type:id_token indicates that the subject token type is an ID token. |
requested_token_type | The value urn:shopify:params:oauth:token-type:offline-access-token for requesting offline access tokens. |
expiring |
|
For more details on token exchange, refer to Exchange a session token for an access token.
Anchor to ExampleExample
The following example shows how to exchange a session token for an expiring offline access token. The response returns an access token, a refresh token, and their respective expiration times.
Request
Response
When exchanging an authorization code for an offline access token, include the expiring=1 parameter:
| Parameter | Description |
|---|---|
client_idrequired | The client ID for the app, as configured in the Dev Dashboard. |
client_secretrequired | The client secret for the app, as configured in the Dev Dashboard. |
coderequired | The authorization code provided in the redirect. |
expiring | Only applicable if the initial https://{shop}/admin/oauth/authorize request was for an offline token
|
For more details on the authorization code grant flow, refer to Implement authorization code grant manually.
Anchor to ExampleExample
The following example demonstrates exchanging an authorization code for an expiring offline access token. The response returns an access token, a refresh token, and their respective expiration times.
Request
Response
Anchor to Refreshing expiring offline tokensRefreshing expiring offline tokens
When an expiring offline token expires, use the refresh token to obtain a new access token and refresh token.
| Parameter | Description |
|---|---|
client_idrequired | The API key for the app. |
client_secretrequired | The client secret for the app. |
grant_typerequired | The value refresh_token indicates that a refresh token grant is being used. |
refresh_tokenrequired | The refresh token received when the access token was issued. |
Anchor to ExampleExample
The following example demonstrates using a refresh token to obtain new tokens. The response returns a new access token and a new refresh token, both with updated expiration times.
Request
Response
Anchor to Refresh token behaviorRefresh token behavior
When you refresh an expiring offline token, the following behavior applies:
- New tokens on each refresh: Both access token and refresh token are regenerated.
- Extended expiration: A new refresh token has a new 90-day expiration from the refresh time.
- One-time use: The previous refresh token is invalidated after use.
If the refresh token expires (after 90 days), the merchant will need to re-launch the app for the app to re-trigger the token acquisition flow.
Anchor to Migrating from non-expiring to expiring tokensMigrating from non-expiring to expiring tokens
If your app currently uses non-expiring offline tokens, you can migrate to expiring tokens to enhance security.
Anchor to Step 1: Update your app's session storageStep 1: Update your app's session storage
Add fields to store token expiration metadata:
expires_at: When the access token expiresrefresh_token: The refresh token valuerefresh_token_expires_at: When the refresh token expires
Anchor to Step 2: Implement token refresh logicStep 2: Implement token refresh logic
Before making API requests where no user interaction is involved, like a background job, check if the offline access token has expired and refresh it if needed using the refresh token.
Anchor to Step 3: Start requesting expiring offline tokensStep 3: Start requesting expiring offline tokens
For new installs, start acquiring expiring offline tokens, and persist the refresh token for refreshing.
Anchor to Step 4: Migrate existing tokensStep 4: Migrate existing tokens
For installed shops with existing non-expiring tokens, perform the migration using token exchange. The migration can be done via a background job or during the next app launch.
The original non-expiring token will be revoked upon successful exchange. This migration is irreversible. To obtain a new non-expiring offline access token, the app would have to re-trigger the token acquisition flow with merchant interaction.
| Parameter | Description |
|---|---|
client_idrequired | The API key for the app. |
client_secretrequired | The client secret for the app. |
grant_typerequired | The value urn:ietf:params:oauth:grant-type:token-exchange indicates that token exchange is to be performed. |
subject_tokenrequired | The non-expiring offline access token to migrate. |
subject_token_typerequired | The value urn:shopify:params:oauth:token-type:offline-access-token indicates that the subject token is an offline access token. |
requested_token_typerequired | The value urn:shopify:params:oauth:token-type:offline-access-token for requesting an offline access token. |
expiringrequired | Must be set to 1 to request an expiring offline token. |
Anchor to ExampleExample
The following example shows how to exchange a non-expiring offline token for an expiring one. The response returns a new expiring access token and refresh token, and the original non-expiring token is revoked.