What is the location of your example repository?
No response
Which package or tool is having this issue?
Hydrogen
What version of that package or tool are you using?
shopify/hydrogen@2026.10.0-preview.3
What version of React Router 7 are you using?
Framework: Next.js 16 App Router, via ShopifyScripts/privacyBanner from @shopify/hydrogen/react (not Remix/Hydrogen's Oxygen runtime)
Steps to Reproduce
- Set up a headless Next.js storefront with ShopifyScripts (analytics={{channel: 'headless'}}, consent={{mode: 'default-banner'}}), plus a same-origin SFAPI proxy (handleShopifyRoutes in middleware) per the docs.
- Call window.privacyBanner.showBanner() with no arguments, as the published types require.
- Observe the banner's bannerQuery request to /api/{version}/graphql.json fail with "Online Store channel is locked" — no x-shopify-storefront-access-token header is ever attached.
- Pass {storefrontAccessToken, checkoutRootDomain, storefrontRootDomain, locale, country} into showBanner(...) (requires bypassing the current types with a cast) — the same request now succeeds.
Expected Behavior
- (a) The types should reflect the actual required options for headless stores, and the docs should call out that headless setups must pass storefrontAccessToken (etc.) into showBanner()/showPreferences(), or
- (b) showBanner()/showPreferences() should resolve a token automatically for headless setups the same way other ShopifyScripts-driven consent calls do (e.g. via the #shopify-features liquid-token fallback, or by reusing whatever token/config was already given to ShopifyScripts itself), so headless callers don't have to manually re-supply a token the app already gave to ShopifyScripts.
Actual Behavior
dist/globals.d.mts types window.privacyBanner as:
type ShopifyPrivacyBanner = {
showPreferences: () => Promise<void>;
showBanner: () => Promise<void>;
};
implying no configuration is needed once ShopifyScripts has bootstrapped the page. But in the compiled storefront-banner.js (https://cdn.shopify.com/shopifycloud/privacy-banner/storefront-banner.js), both methods are thin wrappers around the same internal loader the old loadBanner(options) API used:
n.showBanner = function () {
return t(this, arguments, void 0, function (n) {
// ...
return [4, rr(e(e({}, n), { showPreferences: !1, forceShow: !0 }))];
});
};
For a headless storefront (window.Shopify.customerPrivacy.config.isHeadless === true, set when ShopifyScripts is given analytics={{channel: 'headless'}}), the internal getServerData(...) call used to fetch the banner's own content explicitly skips the #shopify-features liquid-token fallback that non-headless/non-banner consent calls use:
this.accessToken = null != e ? e : a ? void 0 : this.liquidAccessToken();
// a === isHeadless -> skips liquidAccessToken() entirely, accessToken stays undefined
So unless a storefrontAccessToken (plus checkoutRootDomain/storefrontRootDomain/locale/country) is explicitly passed into showBanner(options)/showPreferences(options), the banner's own bannerQuery request (proxied through the app's same-origin SFAPI proxy, per the documented headless request-handler pattern) goes out with no Storefront Access Token attached at all, and the Storefront API responds with:
{"errors":[{"message":"Online Store channel is locked.","extensions":{"code":"BAD_REQUEST"}}]}
This is confusing for three reasons:
- The TypeScript types don't allow passing these options at all — following the types leads directly into this failure.
- The error message ("Online Store channel is locked") gives no hint that a missing access token is the actual cause.
- Current headless docs (e.g.
shopify.dev/docs/storefronts/headless/hydrogen/analytics/consent, and the hydrogen-analytics skill bundled with the package) document showBanner()/showPreferences() as needing no arguments, with no mention of a headless-specific requirement.
What is the location of your example repository?
No response
Which package or tool is having this issue?
Hydrogen
What version of that package or tool are you using?
shopify/hydrogen@2026.10.0-preview.3
What version of React Router 7 are you using?
Framework: Next.js 16 App Router, via ShopifyScripts/privacyBanner from @shopify/hydrogen/react (not Remix/Hydrogen's Oxygen runtime)
Steps to Reproduce
Expected Behavior
Actual Behavior
dist/globals.d.mtstypeswindow.privacyBanneras:implying no configuration is needed once
ShopifyScriptshas bootstrapped the page. But in the compiledstorefront-banner.js(https://cdn.shopify.com/shopifycloud/privacy-banner/storefront-banner.js), both methods are thin wrappers around the same internal loader the oldloadBanner(options)API used:For a headless storefront (
window.Shopify.customerPrivacy.config.isHeadless === true, set whenShopifyScriptsis givenanalytics={{channel: 'headless'}}), the internalgetServerData(...)call used to fetch the banner's own content explicitly skips the#shopify-featuresliquid-token fallback that non-headless/non-banner consent calls use:So unless a
storefrontAccessToken(pluscheckoutRootDomain/storefrontRootDomain/locale/country) is explicitly passed intoshowBanner(options)/showPreferences(options), the banner's ownbannerQueryrequest (proxied through the app's same-origin SFAPI proxy, per the documented headless request-handler pattern) goes out with no Storefront Access Token attached at all, and the Storefront API responds with:{"errors":[{"message":"Online Store channel is locked.","extensions":{"code":"BAD_REQUEST"}}]}This is confusing for three reasons:
shopify.dev/docs/storefronts/headless/hydrogen/analytics/consent, and thehydrogen-analyticsskill bundled with the package) documentshowBanner()/showPreferences()as needing no arguments, with no mention of a headless-specific requirement.