You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On Hydrogen storefronts with a checkout subdomain (the standard setup — storefront on the apex, checkout on a subdomain like checkout.mystore.com), the Customer Privacy banner is fundamentally broken:
The hosted banner UI (storefront-banner.js) and the core SDK (consent-tracking-api.js, v0.1 and v0.2) both build an SFAPI URL using Hydrogen's dot-prefixed storefrontRootDomain as a hostname → https://.mystore.com/api/unstable/graphql.json → ERR_NAME_NOT_RESOLVED.
The banner script crashes during init → window.Shopify.customerPrivacy.setTrackingConsent / currentVisitorConsent never get installed.
The core SDK chains its cookie write after the failed SFAPI fetch's .then() → _tracking_consent is never persisted.
User-visible symptoms:
Banner reappears on every refresh (the consent decision doesn't stick).
Google Consent Mode v2 status reverts from gcs=G111 (granted) → gcs=G100 (denied) on the second page view.
CSP connect-src violation reports for the bogus dot-host URL.
I've verified this on Hydrogen 2026.5.4 against a clean Pilot template clone, and against a production Hydrogen 2026.4 storefront.
Why Hydrogen can't fully fix this from inside the npm package
The dot prefix on storefrontRootDomain is intentional — it's only meant for cookie Domain= scoping so _tracking_consent is readable across subdomains. The bug is that the SDK scripts (hosted on cdn.shopify.com, outside this repo) misuse the same string as a URL hostname.
Hydrogen even has a comment acknowledging the underlying limitation:
// app/dist/development/index.cjs (and equivalent in production bundle)//// "Prefix with a dot to ensure this domain is different from checkoutRootDomain.// This will ensure old cookies are set for a cross-subdomain checkout setup// so that we keep backward compatibility until new cookies are rolled out.// Once consent-tracking-api is updated to not rely on cookies anymore, we can// remove this."storefrontRootDomain: commonAncestorDomain ? "."+commonAncestorDomain : void0,
So Hydrogen knows the SDK has a coupling problem here. But until the SDK is updated, every Hydrogen merchant with a checkout subdomain is shipping a broken consent banner without realising it.
Reproduction
Any Hydrogen 2026.x template + a checkout subdomain:
Clone the Pilot template, configure with PUBLIC_CHECKOUT_DOMAIN=checkout.<apex> where <apex> ≠ myshopify.com.
Deploy to a real custom domain (the bug doesn't reproduce on *.myshopify.com because in that case storefrontRootDomain is undefined — commonAncestorDomain returns falsy).
Open the deployed storefront in incognito, click "Accept" on the banner.
Open DevTools → Console. Observe:
storefront-banner.js:3 POST https://.<apex>/api/unstable/graphql.json net::ERR_NAME_NOT_RESOLVED
Open DevTools → Application → Cookies. Observe: no _tracking_consent cookie despite clicking Accept.
// storefrontRootDomain used as URL hoststorefrontRootDomain||window.location.host
When setTrackingConsent is called (whether by the banner or by user code), the SDK POSTs to the same broken URL. The cookie write is chained inside the .then() of that fetch — so on DNS failure the cookie is never set.
What works today (sameDomainForStorefrontApi: true)
fixes checkoutRootDomain (Path 1 above) so the SDK's GraphQL client routes through the storefront's same-domain SFAPI proxy. But it doesn't help with the banner script's Path 2 or the core SDK's cookie-write path, because those use storefrontRootDomain unconditionally, ignoring sameDomainForStorefrontApi.
withPrivacyBanner: false to skip loading storefront-banner.js entirely. The core SDK still loads.
Custom <ConsentBanner /> component renders on absence of _tracking_consent cookie.
On Accept/Decline, write _tracking_consent directly in the exact serialized format the SDK uses (reverse-engineered from v0.2's S() function: unquoted object keys, JSON.stringify for strings, toString for booleans, omit undefined/empty fields). The Shopify checkout reads this cookie via Domain=.<apex> scoping and honors it.
Dispatch visitorConsentCollected with the standard VisitorConsentCollected shape so downstream listeners (GCM v2 updaters, custom analytics) work unchanged.
This is the minimum viable workaround that keeps the rest of the Customer Privacy contract intact. But every Hydrogen merchant with a subdomain checkout would need to ship something like this — that's a big footgun for a default behaviour.
Suggested fixes
In rough order of how much they'd help:
Fix the SDK scripts on cdn.shopify.com — the canonical fix. Both storefront-banner.js Path 2 and consent-tracking-api.js's SFAPI URL build should derive the host from checkoutRootDomain (or window.location.host if same-domain), never from storefrontRootDomain. That field's purpose is purely cookie scoping. We can't PR this, but presumably someone on the Customer Privacy team can.
Document the bug in @shopify/hydrogen until [Track] Accounts #1 lands. A note in the Customer Privacy guide saying "If your checkout is on a subdomain, withPrivacyBanner: true is currently broken — set it to false and provide your own banner; here's a reference implementation" would save every Hydrogen team weeks of debugging.
Expose a first-class custom-banner pattern in @shopify/hydrogen. Either a <CustomerPrivacy.Banner> component or a useCustomerPrivacyBanner() hook that produces the cookie + event dispatch the way our workaround does, so individual stores don't each reinvent it.
Happy to PR any of (2) or (3) if useful — let us know which would be most helpful to land.
Summary
On Hydrogen storefronts with a checkout subdomain (the standard setup — storefront on the apex, checkout on a subdomain like
checkout.mystore.com), the Customer Privacy banner is fundamentally broken:storefront-banner.js) and the core SDK (consent-tracking-api.js, v0.1 and v0.2) both build an SFAPI URL using Hydrogen's dot-prefixedstorefrontRootDomainas a hostname →https://.mystore.com/api/unstable/graphql.json→ERR_NAME_NOT_RESOLVED.window.Shopify.customerPrivacy.setTrackingConsent/currentVisitorConsentnever get installed..then()→_tracking_consentis never persisted.User-visible symptoms:
gcs=G111(granted) →gcs=G100(denied) on the second page view.window.Shopify.customerPrivacy.setTrackingConsent === undefinedpost-banner-load.connect-srcviolation reports for the bogus dot-host URL.I've verified this on Hydrogen
2026.5.4against a clean Pilot template clone, and against a production Hydrogen2026.4storefront.Why Hydrogen can't fully fix this from inside the npm package
The dot prefix on
storefrontRootDomainis intentional — it's only meant for cookieDomain=scoping so_tracking_consentis readable across subdomains. The bug is that the SDK scripts (hosted oncdn.shopify.com, outside this repo) misuse the same string as a URL hostname.Hydrogen even has a comment acknowledging the underlying limitation:
So Hydrogen knows the SDK has a coupling problem here. But until the SDK is updated, every Hydrogen merchant with a checkout subdomain is shipping a broken consent banner without realising it.
Reproduction
Any Hydrogen 2026.x template + a checkout subdomain:
Clone the Pilot template, configure with
PUBLIC_CHECKOUT_DOMAIN=checkout.<apex>where<apex>≠myshopify.com.Deploy to a real custom domain (the bug doesn't reproduce on
*.myshopify.combecause in that casestorefrontRootDomainis undefined —commonAncestorDomainreturns falsy).Open the deployed storefront in incognito, click "Accept" on the banner.
Open DevTools → Console. Observe:
Open DevTools → Application → Cookies. Observe: no
_tracking_consentcookie despite clicking Accept.Refresh. Banner reappears. GA4
collectURLgcs=G100.Root cause in the SDK source
cdn.shopify.com/shopifycloud/privacy-banner/storefront-banner.jsTwo call paths build the SFAPI URL:
The
re()function:When
n === ".mystore.com"(Path 2), the final URL ishttps://.mystore.com/...→ invalid hostname → DNS fails.cdn.shopify.com/shopifycloud/consent-tracking-api/v0.2/consent-tracking-api.jsSame anti-pattern exists in the core SDK too:
When
setTrackingConsentis called (whether by the banner or by user code), the SDK POSTs to the same broken URL. The cookie write is chained inside the.then()of that fetch — so on DNS failure the cookie is never set.What works today (
sameDomainForStorefrontApi: true)Setting this on the consent config:
fixes
checkoutRootDomain(Path 1 above) so the SDK's GraphQL client routes through the storefront's same-domain SFAPI proxy. But it doesn't help with the banner script's Path 2 or the core SDK's cookie-write path, because those usestorefrontRootDomainunconditionally, ignoringsameDomainForStorefrontApi.Workaround we landed downstream
Pilot PR (Weaverse/pilot#387) — full source, ~250 lines net:
withPrivacyBanner: falseto skip loadingstorefront-banner.jsentirely. The core SDK still loads.<ConsentBanner />component renders on absence of_tracking_consentcookie._tracking_consentdirectly in the exact serialized format the SDK uses (reverse-engineered from v0.2'sS()function: unquoted object keys,JSON.stringifyfor strings,toStringfor booleans, omitundefined/empty fields). The Shopify checkout reads this cookie viaDomain=.<apex>scoping and honors it.visitorConsentCollectedwith the standardVisitorConsentCollectedshape so downstream listeners (GCM v2 updaters, custom analytics) work unchanged.This is the minimum viable workaround that keeps the rest of the Customer Privacy contract intact. But every Hydrogen merchant with a subdomain checkout would need to ship something like this — that's a big footgun for a default behaviour.
Suggested fixes
In rough order of how much they'd help:
Fix the SDK scripts on
cdn.shopify.com— the canonical fix. Bothstorefront-banner.jsPath 2 andconsent-tracking-api.js's SFAPI URL build should derive the host fromcheckoutRootDomain(orwindow.location.hostif same-domain), never fromstorefrontRootDomain. That field's purpose is purely cookie scoping. We can't PR this, but presumably someone on the Customer Privacy team can.Document the bug in
@shopify/hydrogenuntil [Track] Accounts #1 lands. A note in the Customer Privacy guide saying "If your checkout is on a subdomain,withPrivacyBanner: trueis currently broken — set it tofalseand provide your own banner; here's a reference implementation" would save every Hydrogen team weeks of debugging.Expose a first-class custom-banner pattern in
@shopify/hydrogen. Either a<CustomerPrivacy.Banner>component or auseCustomerPrivacyBanner()hook that produces the cookie + event dispatch the way our workaround does, so individual stores don't each reinvent it.Happy to PR any of (2) or (3) if useful — let us know which would be most helpful to land.
Environment
@shopify/hydrogen: 2026.5.4 (verified) + 2026.4.x (also affected)mainconsent-tracking-api/v0.1/andconsent-tracking-api/v0.2/bundles oncdn.shopify.com