What permission is required to use storefrontAccessTokenCreate mutation in Shopify Admin API?

Hi @Dylan and everyone,

I’m trying to use the following mutation to create a Storefront Access Token via the Shopify Admin API:

mutation storeFrontAccessTokenCreate($input: StorefrontAccessTokenInput!) {
  storefrontAccessTokenCreate(input: $input) {
    storefrontAccessToken {
      id
      accessToken
      accessScopes {
        handle
      }
      createdAt
      title
    }
    userErrors {
      field
      message
    }
  }
}

However, I’m getting this error:

{
  "errors": [
    {
      "message": "Access denied for storefrontAccessTokenCreate field.",
      "extensions": {
        "code": "ACCESS_DENIED",
        "documentation": "https://shopify.dev/api/usage/access-scopes"
      }
    }
  ],
  "data": {
    "storefrontAccessTokenCreate": null
  }
}

I’ve verified that my app is using the Admin API, and this mutation appears to be supported. Can anyone clarify which specific Admin API access scope or permission is required to successfully use this mutation? Also, does this mutation only work with custom apps or public apps as well?

Thanks in advance!

Please see this relevant discussion that answers this question:

2 Likes

Just to add to this as well, I recently had some frustrations with that mutation. The issue I ran in to is I hadn’t enabled the storefront API access in the partner dashboard, so that’s another thing to check :slight_smile:

1 Like

Hi @KyleG-Shopify

Appreciate the heads-up! Could you please specify where exactly in the Partner Dashboard the Storefront API access needs to be enabled?

It’s in the app configuration for custom apps.

1 Like

Hi @KyleG-Shopify,
Thanks so much for your help — I really value your input. Just wondering, is it possible to access the Storefront API section from the public app?

For a public app, adding the scopes to your TOML should be sufficient.

1 Like

Hi @Kyleg-Shopify
Thank you for the heads-up.

I’m currently using the following scopes in my public app, but I’m still unable to get checkout access:

read_all_orders,read_cart_transforms,read_content,read_customer_events,read_customer_merge,read_customers,read_delivery_customizations,read_discounts,read_discovery,read_draft_orders,read_fulfillments,read_inventory,read_markets,read_order_edits,read_orders,read_price_rules,read_product_listings,read_products,read_publications,read_themes,read_translations,write_cart_transforms,write_content,write_customer_merge,write_customers,write_delivery_customizations,write_discounts,write_discovery,write_draft_orders,write_fulfillments,write_inventory,write_markets,write_order_edits,write_orders,write_payment_terms,write_product_listings,write_products,write_publications,write_themes,write_translations

For the storefront API, that uses the unauthenticated access scopes, which I’m not seeing in that list: https://shopify.dev/docs/api/usage/access-scopes#unauthenticated-access-scopes

Can you clarify what you’re needing with checkout access? The reason I ask is to make sure you’re not looking to access the checkout API’s. Checkout APIs will be shut down April 1, 2025 - Shopify developer changelog

1 Like

Hi @KyleG-Shopify,

Thank you for the clarification, and I apologize for the confusion regarding the access scopes. You’re absolutely right—I mistakenly listed only the authenticated Admin API scopes and overlooked the unauthenticated access scopes required for the Storefront API.

"read_all_cart_transforms,read_all_orders,read_channels,read_checkouts,read_customer_events,read_customers,read_delivery_customizations,read_discovery,read_draft_orders,read_inventory,read_legal_policies,read_locales,read_locations,read_marketing_events,read_markets,read_order_edits,read_orders,read_product_listings,read_products,read_publications,read_shipping,read_translations,unauthenticated_read_checkouts,unauthenticated_read_customers,unauthenticated_read_product_listings,unauthenticated_write_checkouts,unauthenticated_write_customers,write_checkouts,write_customers,write_delivery_customizations,write_discovery,write_draft_orders,write_inventory,write_markets,write_order_edits,write_orders,write_payment_terms,write_product_listings,write_products,write_publications,write_translations"

To clarify, I’m not trying to access the deprecated Checkout APIs. I’m building a mobile app builder and aiming to use the Storefront Cart API to enable cart functionality within the app experience. For this, I understand I need a Storefront Access Token—but I’m running into an issue generating one via the Admin API.

I’m using the storefrontAccessTokenCreate mutation, but I’m getting an Access Denied error.

info- My app is already a Sales Channel

Thanks again for your guidance!

Thanks for sharing that. Are you using managed installations for your app? If so, try adding the following direct api fields to your app configuration and then deploy:

[api]
direct_api_mode = "offline"
embedded_app_direct_api_access = true

And then test using an external API client postman to ensure the offline mode will be used.

1 Like

Hi @KyleG-Shopify,

Thanks for your guidance.

Yes, I’m using managed installations, and I’ve added the direct_api_mode = "offline" and embedded_app_direct_api_access = true fields to my app configuration under the [api] section as you suggested. I also tested the API using Postman to confirm it’s using offline mode.

Here is my current .toml configuration for reference:

# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration

client_id = "xxxxx***####"
name = "Mobile App Builder"
handle = "mobile-app-cli-node"
application_url = "https://xxxxx***####.com/"
embedded = true

[api]
direct_api_mode = "offline"
embedded_app_direct_api_access = true

[build]
automatically_update_urls_on_dev = false
include_config_on_deploy = true

[webhooks]
api_version = "2025-04"

  [[webhooks.subscriptions]]
  uri = "/webhooks/customers/data_request"
  compliance_topics = [ "customers/data_request" ]

  [[webhooks.subscriptions]]
  uri = "/webhooks/customers/redact"
  compliance_topics = [ "customers/redact" ]

  [[webhooks.subscriptions]]
  uri = "/webhooks/shop/redact"
  compliance_topics = [ "shop/redact" ]

[access_scopes]
# Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes
scopes = "read_all_cart_transforms,read_all_orders,read_channels,read_checkouts,read_customer_events,read_customers,read_delivery_customizations,read_discovery,read_draft_orders,read_inventory,read_legal_policies,read_locales,read_locations,read_marketing_events,read_markets,read_order_edits,read_orders,read_product_listings,read_products,read_publications,read_shipping,read_translations,unauthenticated_read_checkouts,unauthenticated_read_customers,unauthenticated_read_product_listings,unauthenticated_write_checkouts,unauthenticated_write_customers,write_checkouts,write_customers,write_delivery_customizations,write_discovery,write_draft_orders,write_inventory,write_markets,write_order_edits,write_orders,write_payment_terms,write_product_listings,write_products,write_publications,write_translations"

[auth]
redirect_urls = [
  "https://xxxxx***####.com//auth/callback",
  "https://xxxxx***####.com//auth/shopify/callback",
  "https://xxxxx***####.com//api/auth/callback"
]

[pos]
embedded = false

Despite these changes, I’m still encountering the following error when trying to access the storefrontAccessTokenCreate field:

Could you please advise if any additional permissions or scopes are needed to access this mutation? Let me know if I should regenerate the tokens or redeploy the app after these changes.

Thanks again for your support!

Thanks. Can you try a client separate from the integrated Graphiql to see if that works?

1 Like

Hi @KyleG-Shopify and @Dylan,

Thank you both so much for your guidance and continued support throughout this issue. Your insights about unauthenticated access scopes, enabling Storefront API in the Partner Dashboard, and configuring the .toml file with direct_api_mode = "offline" and embedded_app_direct_api_access = true have been incredibly helpful.

I truly appreciate the time you’ve taken to help clarify the requirements for using the storefrontAccessTokenCreate mutation and navigating the nuances between public apps and custom apps.

One small question:
Currently, I’m using the following configuration:

toml

[api]
direct_api_mode = "offline"
embedded_app_direct_api_access = true

When my app goes live, do I need to switch direct_api_mode from "offline" to "online", or should I keep it as "offline"?

Thanks again!

1 Like

You may need offline access to request the storefront access token. Definitely worth testing if you weren’t planning on using offline.

1 Like

Big thank you to @KyleG-Shopify and @Dylan for your continued support and clear guidance throughout this troubleshooting journey! :folded_hands:

Your insights around:

  • Enabling Storefront API in the Partner Dashboard
  • Ensuring the correct unauthenticated access scopes
  • Using direct_api_mode = "offline" and embedded_app_direct_api_access = true

…were exactly what I needed to resolve the ACCESS_DENIED error when using the storefrontAccessTokenCreate mutation.

:white_check_mark: Solution Summary

This .toml config works for me when generating a Storefront Access Token:

# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration

client_id = "xxxxx***####"
name = "Mobile App Builder"
handle = "mobile-app-cli-node"
application_url = "https://xxxxx***####.com/"
embedded = true

[api]
direct_api_mode = "offline"
embedded_app_direct_api_access = true

[build]
automatically_update_urls_on_dev = false
include_config_on_deploy = true

[webhooks]
api_version = "2025-04"

  [[webhooks.subscriptions]]
  uri = "/webhooks/customers/data_request"
  compliance_topics = [ "customers/data_request" ]

  [[webhooks.subscriptions]]
  uri = "/webhooks/customers/redact"
  compliance_topics = [ "customers/redact" ]

  [[webhooks.subscriptions]]
  uri = "/webhooks/shop/redact"
  compliance_topics = [ "shop/redact" ]

[access_scopes]
scopes = "read_all_cart_transforms,read_all_orders,read_channels,read_checkouts,read_customer_events,read_customers,read_delivery_customizations,read_discovery,read_draft_orders,read_inventory,read_legal_policies,read_locales,read_locations,read_marketing_events,read_markets,read_order_edits,read_orders,read_product_listings,read_products,read_publications,read_shipping,read_translations,unauthenticated_read_checkouts,unauthenticated_read_customers,unauthenticated_read_product_listings,unauthenticated_write_checkouts,unauthenticated_write_customers,write_checkouts,write_customers,write_delivery_customizations,write_discovery,write_draft_orders,write_inventory,write_markets,write_order_edits,write_orders,write_payment_terms,write_product_listings,write_products,write_publications,write_translations"

[auth]
redirect_urls = [
  "https://xxxxx***####.com//auth/callback",
  "https://xxxxx***####.com//auth/shopify/callback",
  "https://xxxxx***####.com//api/auth/callback"
]

[pos]
embedded = false

Use the Correct API Client

  • :cross_mark: Does not work in:
    • Shopify Admin GraphiQL (admin/api/graphql)
  • :white_check_mark: Works only in:
    • GraphiQL API client inside a Remix Shopify CLI app project or Associate Shopify product

here is the e.g.


import { authenticate } from "../../../shopify.server";

/**
 * Creates a new Storefront Access Token using Shopify's GraphQL API.
 * @param {Request} request - The incoming request object (for authentication).
 * @param {Object} input - The StorefrontAccessTokenInput object.
 * @returns {Promise<Object>} The response from Shopify containing the access token or user errors.
 */
export const createStorefrontAccessToken = async (request, input) => {
  const { admin } = await authenticate.admin(request);

  const mutation = `
    mutation storeFrontAccessTokenCreate($input: StorefrontAccessTokenInput!) {
      storefrontAccessTokenCreate(input: $input) {
        storefrontAccessToken {
          id
          accessToken
          accessScopes {
            handle
          }
          createdAt
          title
        }
        userErrors {
          field
          message
        }
      }
    }
  `;

  const variables = { input };

  const response = await admin.graphql(mutation, { variables });
  const result = await response.json();

  return result.data?.storefrontAccessTokenCreate || {
    storefrontAccessToken: null,
    userErrors: [{ message: "No response from Shopify" }]
  };
};

:light_bulb: Notes:

  • storefrontAccessTokenCreate requires an embedded app session with proper context — that’s why it only works in Remix-based CLI projects running via shopify app dev.
  • This is likely due to how Shopify enforces permission context and session token structure for that mutation.
  • No extra scopes beyond unauthenticated Storefront API scopes are needed if your app is a Sales Channel.
  • Keep direct_api_mode = "offline" to allow proper background access.
  • In the Shopify Partner Dashboard, designate your app as a Sales Channel. This enables access to specific Storefront API features (if you are trying the checkout and cart apis).

:folded_hands: Special Thanks

Massive thanks to @KyleG-Shopify and @Dylan for their insights and guidance throughout the troubleshooting. Your help navigating the tricky parts of Storefront API permissions is greatly appreciated!

1 Like