Data and tags
Shopify
The c15t Shopify bridge sends each consent choice to Shopify's Customer
Privacy API. Shopify's analytics, and the apps and pixels that read that API,
then follow your c15t banner instead of Shopify's own. The bridge loads no
script. Hydrogen's ShopifyScripts loads the Customer Privacy API, and the
bridge's setup function holds Hydrogen's analytics events until Shopify has
the visitor's choice.
The Shopify example runs the code on this page in a Next.js app against mock.shop.
Add the bridge
Set up c15t with the Next.js quickstart first, then install the integrations package:
Create the bridge once per page and pass its setup to ShopifyScripts with
mode: 'custom-banner'. A second component feeds the bridge each c15t
snapshot:
Render ShopifyAnalytics in the document head, as Hydrogen requires, with
your shop's details. Render ShopifyConsentSync inside ConsentRoot:
Declare the categories
c15t's dialog offers only the categories that something on the page uses.
Shopify's scripts load through ShopifyScripts, not through c15t's scripts
option, so c15t cannot find them. Declare the categories the bridge maps, or
the dialog shows only Necessary and Shopify receives a denial for everything:
The categories must also be in your policy's scope.
Serve the Storefront API route
The Customer Privacy API saves each choice by posting to the storefront's own
/api/<version>/graphql.json. Hydrogen's handleShopifyRoutes serves that
route, forwards it to your shop and keeps the cookies Shopify sets. In
Next.js, call it from proxy.ts:
The consent route needs no handlers. The example adds Hydrogen's cart
handlers for its cart, and its lib/shop.ts holds the store domain and
market. It has no customer accounts, so it passes a session manager that
stores nothing. Pass your own if your storefront signs customers in.
How categories map
| Shopify consent | c15t category |
|---|---|
analytics | measurement |
marketing | marketing |
preferences | functionality |
sale_of_data | marketing |
Each Shopify value is true only while its c15t category is allowed in the
effective permissions, which include policy restrictions. Pass categories
to change the mapping:
toShopifyConsent(permissions, categories) returns the same four values for
your own code.
When the bridge sends
Shopify records consent only when the visitor makes a choice, so the bridge sends nothing until then, or while the policy is pending.
- Opt-in or IAB policy:
setupwaits until the visitor answers the banner, including with Reject All, so Hydrogen holds events until then. - Opt-out policy:
setupresolves without recording a choice, and Shopify applies its region defaults, including Global Privacy Control, until the visitor saves a choice in c15t. A stale grant is the exception, below.
After the first choice, the bridge sends each change once. When a visitor revokes a category, c15t reloads the page and the bridge sends the stored choice on load.
While the visitor has no current choice, Shopify may still hold grants from
an earlier one, for example after that choice expired or a policy change
asks again. If c15t now denies any of them, the bridge sends false for
those grants only, so Shopify keeps the other signals, including unset ones.
It counts grants the bridge sent and each yes from Shopify's
currentVisitorConsent(), and sends the revocation again if Shopify reports
a dropped grant again, for example after another tab granted it. If that
function isn't available, it assumes a choice c15t stored may have granted
anything. Under an opt-in or IAB policy, Hydrogen keeps holding events until
the visitor answers. Under opt-out, setup resolves once Shopify accepts the
revocation.
If the Customer Privacy API hasn't loaded yet, the bridge waits for
Shopify's consentTrackingApiLoaded or shopifyCustomerPrivacyApiLoaded
event.
setup resolves once Shopify has accepted the visitor's latest choice, and
Hydrogen then delivers the events that choice allows. If the visitor changes
their mind while the first request is in flight, setup waits for the newer
one. It never rejects, because Hydrogen stops calling setup after a
rejection. When Shopify rejects a choice, the bridge keeps events blocked and
sends it again after 1, 2 and 4 seconds, then on the next update. If the
policy turns pending meanwhile, the bridge drops those retries.
Options
| Option | Default | Behavior |
|---|---|---|
categories | The table above | Overrides the c15t category for any of the four Shopify values. |
onSync | None | Called with what Shopify accepted: the four values for a choice, or the dropped grants for a revocation. |
onError | Logs to the console | Called when Shopify rejects a choice or onSync throws. |
The bridge returns update(snapshot), connect(source) and setup().
connect follows anything with getSnapshot and subscribe, such as a c15t
kernel, and returns a function that stops following it.
Limits
- Checkout runs on Shopify's domain and doesn't render your storefront code. Shopify reads the stored choice there.
- Shopify's analytics need a real shop. mock.shop accepts the consent request
but sets no consent cookie, so
currentVisitorConsent()stays empty. - The bridge maps categories only; it doesn't translate IAB TCF choices.
Verify the bridge
- In a private window with an opt-in policy, load the page. The network panel
shows no
graphql.jsonrequest withvisitorConsent. - Click Reject All. One request goes out with every value
false. - Open the dialog, allow Marketing and save. One request goes out with
marketingandsaleOfDataset totrue. - On a real shop, run
window.Shopify.customerPrivacy.currentVisitorConsent()in the console and check it matches your choice.