Skip to main content

Manage access scopes

Access scopes control what your app can read and write. This guide covers where to declare scopes, whether to make them required or optional, and how to change them after your app is installed. For the full list of scopes, see the access scopes reference. For how your app gets the access token that carries these scopes, see About app authentication.


Anchor to Where you configure scopesWhere you configure scopes

Where you declare your app's scopes depends on how your app is set up:

How your app is set upWhere you declare scopes
Built with Shopify CLIThe [access_scopes] section of your app's configuration TOML file. Run shopify app deploy to deploy the scopes you've modified.
Created in the Dev DashboardYour app's version in the Dev Dashboard. Release the version to deploy the scopes you've modified.
Implements the authorization code grant itselfThe scope parameter of the authorization URL your app builds.

However you declare them, your app can only use a scope once it's been approved on the store. If your app acts only on stores in your own organization, you approve that change yourself when you release the version, so there's no merchant consent step. Examples on this page use the Shopify CLI TOML setup, which is the most common. Where a step differs for the other setups, it's called out.


These scopes cover the most frequently used GraphQL Admin API resources. For the full list, including unauthenticated and customer scopes, see the access scopes reference.

ScopesMain resources
read_products, write_productsProduct, ProductVariant, Collection
read_orders, write_ordersOrder, Fulfillment, OrderTransaction, AbandonedCheckout
read_customers, write_customersCustomer, Segment, Company, CompanyLocation
read_inventory, write_inventoryInventoryLevel, InventoryItem
read_draft_orders, write_draft_ordersDraftOrder
read_discounts, write_discountsDiscounts features
read_content, write_contentArticle, Blog, Comment, Page
read_themes, write_themesOnlineStoreTheme

To find the scope a specific call needs, look up the field or mutation in the GraphQL Admin API reference. Each entry lists the access scopes it requires.

Three things to know before you declare scopes:

  • A write scope includes read. write_products grants read_products, so declare the write scope on its own when your app needs both. Declaring the read scope as optional while the write scope is required fails to deploy. See Moving a scope between scopes and optional_scopes.
  • Orders are limited to the last 60 days. read_orders and write_orders cover that window. To reach older orders, request read_all_orders and declare it alongside them.
  • More scopes reach protected customer data than you'd expect. Customer scopes are the obvious case, but orders, draft orders, fulfillments, abandoned checkouts, and store comments all carry customer data too. You can declare any of them, but outside of dev stores the API redacts protected fields until your app meets the protected customer data requirements and is approved.

None of these choices are permanent. You can change which scopes you declare, and whether each one is required or optional, after your app is installed. See Modify declared scopes.


Anchor to Access scope configurationsAccess scope configurations

There are two ways you can configure your access scopes: scopes, which merchants grant when they install your app, and optional_scopes, which your app requests later and merchants can decline.

Required access scopes. Merchants must grant these when they install your app, so your app is guaranteed to have them after installation.

Define them in the scopes field of your app's TOML file:

# shopify.app.config-name.toml
name = "Example App"
client_id = "a61950a2cbd5f32876b0b55587ec7a27"
application_url = "https://www.app.example.com/"
embedded = true

[access_scopes]
scopes = "read_discounts,write_products"

Scopes your app requests after installation. Merchants can grant or decline them, and can revoke ones they've granted. Use optional scopes to offer features to some stores without requiring the same data access from every install.

Define scopes that your app can request dynamically in the optional_scopes field of your app's TOML file:

# shopify.app.config-name.toml
name = "Example App"
client_id = "a61950a2cbd5f32876b0b55587ec7a27"
application_url = "https://www.app.example.com/"
embedded = true

[access_scopes]
scopes = "" # The `scopes` field is still necessary, but can be empty.
optional_scopes = ["read_discounts", "write_products"]

Your app requests these scopes after installation completes, if it needs them.


Anchor to Modify declared scopesModify declared scopes

To add or remove access scopes for your app, update your app's configuration TOML file and deploy the changes.

  1. Modify the scopes or optional_scopes fields in your app's TOML file to include the access scopes you want.

  2. Deploy the changes by running the following Shopify CLI command:

    Terminal

    shopify app deploy
  3. Optionally, subscribe to the app/scopes_update topic to receive webhooks when the granted scopes change.

If your app doesn't use Shopify CLI, the step you take instead depends on how it's set up. For an app created in the Dev Dashboard, release a new version with the scopes you want. For an app that implements the authorization code grant itself, send the merchant through the authorization URL again with the updated scope list. In every case, merchants are prompted to approve scopes you add.

If you modify the scopes field, then the following happens:

  • Merchants are prompted to approve the updated access scopes when they open your app. The app/scopes_update webhook fires when the merchant approves the changes.
  • If the change reduces scopes, the merchant isn't prompted and your app loses access to those scopes automatically when the merchant opens the app. The app/scopes_update webhook fires when the merchant opens the app.

Anchor to Modifying the ,[object Object], fieldModifying the optional_scopes field

If you modify the optional_scopes field, then the following happens:

  • Your app can start requesting the new access scopes.
  • The granted scopes for your app installation don't change until your app requests the new scopes dynamically and the merchant grants access. The app/scopes_update webhook fires when the merchant approves the changes.

Anchor to Moving a scope between ,[object Object], and ,[object Object]Moving a scope between scopes and optional_scopes

You can change whether a scope is required or optional by moving its handle between the scopes and optional_scopes fields, then deploying the change.

If you move a scope from scopes to optional_scopes, then the following happens:

  • Stores that already granted the scope while it was required keep it. The scope is retained as an approved optional scope, so the merchant isn't re-prompted and the scope isn't revoked when they open your app. You don't need to request the scope again for existing installations.
  • New installations don't receive the scope until your app requests it dynamically, the same as any other optional scope.

A required scope can't implicitly grant an optional one. For example, write_products grants read_products, so if you declare read_products as optional while write_products is still required, then deploying fails with the following error:

Declared optional_scopes [read_products] cannot be implicit required scopes.

To resolve this, move or remove the scope that grants it implicitly. In this example, you'd move or remove write_products.


Anchor to Query currently granted scopesQuery currently granted scopes

Use a GraphQL query to get the currently granted access scopes for your app installation.

Example request to retrieve currently granted access scopes using currentAppInstallation

POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

query {
currentAppInstallation {
accessScopes {
description
handle
}
}
}

Response

{
"data": {
"currentAppInstallation": {
"accessScopes": [
{
"description": "Modify products, variants, and collections",
"handle": "write_products"
},
{
"description": "Read products, variants, and collections",
"handle": "read_products"
}
]
}
}
}

Shopify's API libraries wrap this query in a helper method:

LibraryMethod
App Bridge APIshopify.scopes.query()
React Router APIscopes.query()

Anchor to Request new access scopes dynamicallyRequest new access scopes dynamically

Note

You can only request additional access scopes dynamically if they're configured as optional_scopes in your app's TOML file, and the configuration changes have been deployed. Access scopes configured in the scopes field can't be requested dynamically.

Anchor to Request access scopes using the App Bridge APIRequest access scopes using the App Bridge API

If your app renders in the Shopify admin, use the App Bridge Scopes API to request new access scopes dynamically:

shopify.scopes.request(['read_discounts', 'write_products']);

This asynchronous, client-side method doesn't require a browser redirect. It displays a permission grant modal for the requested scopes on top of your running app.

Example permission grant modal:

Optional scopes request grant modal

Anchor to Request access scopes using a request URL for standalone appsRequest access scopes using a request URL for standalone apps

To request optional scopes dynamically for a standalone app, direct the merchant to the request URL so they can approve the new access scopes:

Browser redirect URL

https://admin.shopify.com/store/{STORE_NAME}/oauth/install?client_id={CLIENT_ID}&optional_scopes={REQUESTED_SCOPES}
Query parameterDescription
STORE_NAMEThe name of the merchant's store.
CLIENT_IDThe app's client ID.
REQUESTED_SCOPESA comma-separated list of access scopes to request. This must be a subset of the declared optional_scopes in your TOML file.

Example request URL:

https://admin.shopify.com/store/my-cool-store/oauth/install?client_id=a61950a2cbd5f32876b0b55587ec7a27&optional_scopes=read_discounts,write_products

Anchor to Request access scopes using the React Router APIRequest access scopes using the React Router API

If your app is built from the React Router app template, call scopes.request() to handle the request server-side instead of building the URL yourself:

const {scopes} = await authenticate.admin(request);
await scopes.request(['read_discounts', 'write_products']);

Anchor to Revoke granted scopes dynamicallyRevoke granted scopes dynamically

Note

You can only revoke scopes that are configured as optional_scopes and that were dynamically granted. Access scopes configured in the scopes field can't be revoked dynamically.

If your app no longer needs certain access scopes from a merchant's store, then we recommend revoking them. This helps avoid a potential data leak if the access token is ever compromised.

Revoke access scopes with a GraphQL mutation:

Example request to revoke granted optional access scopes

POST https://{shop}.myshopify.com/admin/api/{api_version}/graphql.json

mutation {
appRevokeAccessScopes(scopes: ["read_discounts","write_products"]) {
revoked {
handle
}
userErrors {
field
message
}
}
}

Response

{
"data": {
"appRevokeAccessScopes": {
"revoked": [
{
"handle": "read_discounts"
},
{
"handle": "write_products"
}
],
"userErrors": []
}
}
}

Both libraries expose a revoke helper that wraps this mutation:

LibraryMethod
App Bridge APIshopify.scopes.revoke()
React Router APIscopes.revoke()


Was this page helpful?