---
title: Manage access scopes
description: >-
  Configure required and optional access scopes, request and revoke optional
  scopes dynamically, and query the scopes granted to your app.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/authentication-authorization/manage-access-scopes
  md: >-
    https://shopify.dev/docs/apps/build/authentication-authorization/manage-access-scopes.md
---

# 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](https://shopify.dev/docs/api/usage/access-scopes). For how your app gets the access token that carries these scopes, see [About app authentication](https://shopify.dev/docs/apps/build/authentication-authorization).

***

## Where you configure scopes

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

| How your app is set up | Where you declare scopes |
| - | - |
| [Built with Shopify CLI](https://shopify.dev/docs/apps/build/cli-for-apps) | The `[access_scopes]` section of your app's [configuration TOML file](https://shopify.dev/docs/apps/build/cli-for-apps/app-configuration#access_scopes). Run `shopify app deploy` to deploy the scopes you've modified. |
| [Created in the Dev Dashboard](https://shopify.dev/docs/apps/build/dev-dashboard/create-apps-using-dev-dashboard#step-2-create-a-version) | Your app's version in the Dev Dashboard. Release the version to deploy the scopes you've modified. |
| Implements the [authorization code grant](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens#authorization-code-grant) itself | The `scope` parameter of the [authorization URL](https://shopify.dev/docs/apps/build/authentication-authorization/authenticate-standalone-apps#build-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.

***

## Choose your scopes

These scopes cover the most frequently used [GraphQL Admin API](https://shopify.dev/docs/api/admin-graphql) resources. For the full list, including [unauthenticated](https://shopify.dev/docs/api/usage/access-scopes#unauthenticated-access-scopes) and [customer](https://shopify.dev/docs/api/usage/access-scopes#customer-access-scopes) scopes, see the [access scopes reference](https://shopify.dev/docs/api/usage/access-scopes).

| Scopes | Main resources |
| - | - |
| `read_products`, `write_products` | [`Product`](https://shopify.dev/docs/api/admin-graphql/latest/objects/Product), [`ProductVariant`](https://shopify.dev/docs/api/admin-graphql/latest/objects/ProductVariant), [`Collection`](https://shopify.dev/docs/api/admin-graphql/latest/objects/Collection) |
| `read_orders`, `write_orders` | [`Order`](https://shopify.dev/docs/api/admin-graphql/latest/objects/Order), [`Fulfillment`](https://shopify.dev/docs/api/admin-graphql/latest/objects/Fulfillment), [`OrderTransaction`](https://shopify.dev/docs/api/admin-graphql/latest/objects/OrderTransaction), [`AbandonedCheckout`](https://shopify.dev/docs/api/admin-graphql/latest/objects/AbandonedCheckout) |
| `read_customers`, `write_customers` | [`Customer`](https://shopify.dev/docs/api/admin-graphql/latest/objects/Customer), [`Segment`](https://shopify.dev/docs/api/admin-graphql/latest/objects/Segment), [`Company`](https://shopify.dev/docs/api/admin-graphql/latest/objects/Company), [`CompanyLocation`](https://shopify.dev/docs/api/admin-graphql/latest/objects/CompanyLocation) |
| `read_inventory`, `write_inventory` | [`InventoryLevel`](https://shopify.dev/docs/api/admin-graphql/latest/objects/InventoryLevel), [`InventoryItem`](https://shopify.dev/docs/api/admin-graphql/latest/objects/InventoryItem) |
| `read_draft_orders`, `write_draft_orders` | [`DraftOrder`](https://shopify.dev/docs/api/admin-graphql/latest/objects/DraftOrder) |
| `read_discounts`, `write_discounts` | [Discounts features](https://shopify.dev/docs/apps/build/discounts) |
| `read_content`, `write_content` | [`Article`](https://shopify.dev/docs/api/admin-graphql/latest/objects/Article), [`Blog`](https://shopify.dev/docs/api/admin-graphql/latest/objects/Blog), [`Comment`](https://shopify.dev/docs/api/admin-graphql/latest/objects/Comment), [`Page`](https://shopify.dev/docs/api/admin-graphql/latest/objects/Page) |
| `read_themes`, `write_themes` | [`OnlineStoreTheme`](https://shopify.dev/docs/api/admin-graphql/latest/objects/OnlineStoreTheme) |

To find the scope a specific call needs, look up the field or mutation in the [GraphQL Admin API reference](https://shopify.dev/docs/api/admin-graphql). 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`](#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`](https://shopify.dev/docs/api/usage/access-scopes#orders-permissions) 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](https://shopify.dev/docs/apps/launch/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](#modify-declared-scopes).

***

## Access scope configurations

There are two ways you can [configure your access scopes](https://shopify.dev/docs/apps/build/cli-for-apps/app-configuration#access_scopes): `scopes`, which merchants grant when they install your app, and `optional_scopes`, which your app requests later and merchants can decline.

### `scopes`

Required access scopes. Merchants must grant these when they [install your app](https://shopify.dev/docs/apps/build/authentication-authorization/cli-app-authentication), so your app is guaranteed to have them after installation.

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

```toml
# 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"
```

### `optional_scopes`

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](#request-new-access-scopes-dynamically) in the `optional_scopes` field of your app's TOML file:

```toml
# 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](#request-new-access-scopes-dynamically) after installation completes, if it needs them.

***

## Modify declared scopes

To add or remove access scopes for your app, update your app's [configuration TOML file](https://shopify.dev/docs/apps/build/cli-for-apps/app-configuration#access_scopes) 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

   ```terminal
   shopify app deploy
   ```

3. Optionally, subscribe to the [`app/scopes_update`](https://shopify.dev/docs/api/webhooks/latest?reference=toml#list-of-topics-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](https://shopify.dev/docs/apps/build/dev-dashboard/create-apps-using-dev-dashboard#step-2-create-a-version) with the scopes you want. For an app that implements the authorization code grant itself, [send the merchant through the authorization URL again](https://shopify.dev/docs/apps/build/authentication-authorization/authenticate-standalone-apps#confirm-the-granted-scopes) with the updated scope list. In every case, merchants are prompted to approve scopes you add.

### Modifying the `scopes` field

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.

### Modifying the `optional_scopes` field

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

* Your app can start [requesting the new access scopes](#request-new-access-scopes-dynamically).
* 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.

### 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](#request-new-access-scopes-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:

```text
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`.

***

## Query currently granted scopes

Use a [GraphQL query](https://shopify.dev/docs/api/admin-graphql/latest/queries/currentAppInstallation) 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

```graphql
query {
  currentAppInstallation {
    accessScopes {
      description
      handle
    }
  }
}
```

## Response

```json
{
  "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:

| Library | Method |
| - | - |
| App Bridge API | [`shopify.scopes.query()`](https://shopify.dev/docs/api/app-home/apis/authentication-and-data/scopes-api#scopes-propertydetail-query) |
| React Router API | [`scopes.query()`](https://shopify.dev/docs/api/shopify-app-react-router/latest/apis/scopes#scopes-propertydetail-query) |

***

## Request 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.

### Request access scopes using the App Bridge API

If your app renders in the Shopify admin, use the [App Bridge Scopes API](https://shopify.dev/docs/api/app-home/apis/authentication-and-data/scopes-api#scopes-propertydetail-request) to request new access scopes dynamically:

```javascript
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](https://shopify.dev/assets/assets/images/apps/auth/optional-scopes-grant-modal-DKVaj88Z.png)

### Request 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

```text
https://admin.shopify.com/store/{STORE_NAME}/oauth/install?client_id={CLIENT_ID}&optional_scopes={REQUESTED_SCOPES}
```

| Query parameter | Description |
| - | - |
| `STORE_NAME` | The name of the merchant's store. |
| `CLIENT_ID` | The app's client ID. |
| `REQUESTED_SCOPES` | A 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:

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

### Request access scopes using the React Router API

If your app is built from the React Router app template, call [`scopes.request()`](https://shopify.dev/docs/api/shopify-app-react-router/latest/apis/scopes#scopes-propertydetail-request) to handle the request server-side instead of building the URL yourself:

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

***

## Revoke granted scopes dynamically

**Note:**

You can only revoke scopes that are configured as `optional_scopes` and that were [dynamically granted](#request-new-access-scopes-dynamically). 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](https://shopify.dev/docs/api/admin-graphql/latest/mutations/appRevokeAccessScopes):

## Example request to revoke granted optional access scopes

## POST https://{shop}.myshopify.com/admin/api/{api\_version}/graphql.json

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

## Response

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

Both libraries expose a revoke helper that wraps this mutation:

| Library | Method |
| - | - |
| App Bridge API | [`shopify.scopes.revoke()`](https://shopify.dev/docs/api/app-home/apis/authentication-and-data/scopes-api#scopes-propertydetail-revoke) |
| React Router API | [`scopes.revoke()`](https://shopify.dev/docs/api/shopify-app-react-router/latest/apis/scopes#scopes-propertydetail-revoke) |

***

## Next steps

* Look up the full list of scopes and what each one grants in the [access scopes reference](https://shopify.dev/docs/api/usage/access-scopes).
* Learn how to use the `scopes` API in the [App Bridge library](https://shopify.dev/docs/api/app-home/apis/authentication-and-data/scopes-api) or the [React Router library](https://shopify.dev/docs/api/shopify-app-react-router/latest/apis/scopes).
* Review how to declare access scopes in your [app's configuration file](https://shopify.dev/docs/apps/build/cli-for-apps/app-configuration#access_scopes).
* Understand how [access tokens](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens) carry the scopes a merchant granted.

***
