We’re adding an opt-in, storefront-facing feature to an existing published app. It writes a cart metafield from the Online Store (via the Storefront cartMetafieldsSet), which requires the unauthenticated_write_checkouts access scope.
Because only a subset of merchants will use this feature, we wanted to use optional scopes (introduced Dec 2024 for exactly this “progressive permissions” use case) so that only merchants who enable the feature are prompted to grant it. Existing merchants who don’t use it would be unaffected.
That doesn’t appear to be possible. Repro:
-
Declare the scope as optional in
shopify.app.toml:[access_scopes] scopes = "" optional_scopes = [ ..., "unauthenticated_read_checkouts", "unauthenticated_write_checkouts" ] -
shopify app deploy(succeeds, no error/warning about these scopes). -
Request the scope via the standard optional-scope grant flow — App Bridge
shopify.scopes.request([...])or the standalone request URL:https://admin.shopify.com/store/{store}/oauth/install?client_id={client_id}&optional_scopes=unauthenticated_write_checkouts
Result: 400 — Oauth error: undeclared_optional_scopes.
The same flow works perfectly for Admin optional scopes. Requesting e.g. write_discounts (declared optional, not yet granted) via the identical URL redirects straight to the consent screen (/app/grant?access_change_uuid=…). So the flow and the deploy are fine — Shopify simply does not treat unauthenticated scopes as declarable/requestable optional scopes. The only way to obtain unauthenticated_write_checkouts is to put it in the required scopes field.
Why this is a problem for established apps:
Moving a scope into required scopes forces every existing merchant to re-consent to the updated permissions (the app/scopes_update re-prompt) — even though the vast majority will never use the new feature. For a large installed base, prompting everyone to accept a new “read/write checkouts (unauthenticated)” permission for a niche feature is a real adoption/churn risk, and for us it’s a hard stop.
This is the exact scenario optional scopes were meant to solve, and it works for Admin scopes — but the door is closed for anything touching the Storefront API. It effectively means an app must decide up front, at install time, whether it will ever need unauthenticated access, or accept a disruptive global re-prompt later.