What happens when a visitor declines cookies in your Shopify store
· 11 min read
We clicked Decline on a live EU Shopify store's cookie banner. App pixels went quiet; YouTube, chat, analytics and fonts kept loading.
A visitor clicks Decline on your cookie banner. You'd expect the store to go quiet. On a live EU Shopify store we work on, it didn't.
We tested it with three fresh browser profiles from Poland: no choice, Decline, Accept. Each time we logged every request, cookie and storage key on the homepage and a product page.
The apps behaved. The theme did not. Every pixel installed through a Shopify app waited for Accept. Four things in the theme and in app scripts kept loading after Decline, and before any choice at all.
Below are those four leaks in that order, plus a fifth that broke a form. Each one: the problem, how we found it, the fix, the result. At the end, the same check as a list you can run on your own store.
This is a technical check, not legal advice. What your banner must block is a question for your lawyer. What it actually blocks, you can see for yourself.
Why the banner missed them: it only stops code that asks
Shopify's banner stores the visitor's choice in the Customer Privacy API. Shopify's own tools read that choice. So do app pixels and custom pixels that declare a privacy permission (Settings > Customer events). That's why Google, Meta, TikTok, the email tool's tracking and an affiliate pixel all stayed silent until Accept (two of them by their own code, see below).
Code in the theme doesn't ask unless someone built it to. A script pasted into the layout, a YouTube iframe inside a metafield, or a widget injected by an app embed runs whatever the visitor chose. Shopify's help says as much: third-party cookies or pixels added by hand or through apps may need a third-party banner or custom logic to respect consent.
So the banner isn't broken. It just can't see everything. Here's what it couldn't see on this store.
Leak 1: YouTube embeds loaded the player and Google's ad-ID calls
Problem. One product page carried 22 YouTube iframes from four sources: a video modal, product-tab HTML stored in a metafield, video reviews and deferred media. After Decline, 6 players still loaded: 34 requests, 5 youtube.com cookies and calls to Google's doubleclick ad-ID endpoint.
How we found it. Network tab, filter youtube|doubleclick, click Decline, reload. Any row in that list is a fail.
Fix. Each iframe became a click-to-play placeholder. The video loads only after the visitor clicks play or accepts cookies.
Result. The same page after Decline: 22 placeholders, 0 YouTube requests, 0 cookies.
Do it yourself. Paste this where the video should sit (a Custom Liquid block or the page's HTML) in place of the YouTube iframe, and replace VIDEO_ID with the ID from the video's link:
<button type="button" class="yt-play" data-yt="VIDEO_ID" style="width:100%;aspect-ratio:16/9">▶ Play video (loads YouTube)</button>
<script>
function playYouTube(btn, autoplay) {
var f = document.createElement('iframe'); f.title = 'YouTube video'; f.allow = 'autoplay; fullscreen';
f.src = 'https://www.youtube-nocookie.com/embed/' + btn.dataset.yt + (autoplay ? '?autoplay=1' : '');
f.style.cssText = 'width:100%;aspect-ratio:16/9;border:0'; btn.replaceWith(f);
}
function videosIfAllowed() { var p = window.Shopify.customerPrivacy; if (p && p.marketingAllowed()) document.querySelectorAll('.yt-play').forEach(function (b) { playYouTube(b, false); }); }
document.addEventListener('click', function (e) { var b = e.target.closest('.yt-play'); if (b) playYouTube(b, true); });
window.Shopify.loadFeatures([{ name: 'consent-tracking-api', version: '0.1' }], videosIfAllowed);
document.addEventListener('visitorConsentCollected', videosIfAllowed);
</script>
Nothing loads from YouTube until the visitor clicks play or accepts marketing cookies. After Accept, every video on the page loads at once; our store fix waits until each one scrolls into view, which takes more code. youtube-nocookie.com is YouTube's privacy-enhanced mode (opens in a new tab). It changes how views are used, not whether consent is needed, so keep the click.
Leak 2: the chat widget tracked every page view
Problem. A Zendesk-type chat widget loaded on every page. It stored a persistent device ID in local storage and sent a page-view event (URL, title, referrer) to the chat vendor on every page, whatever the visitor chose.
How we found it. Filter Network by the chat vendor's domain, then open Application > Local storage.
Fix. The widget now loads when the visitor clicks the chat launcher, or after consent.
Result. After Decline, only the launcher loads: no vendor requests and no device ID. One click still opens the chat.
Do it yourself. Paste this in layout/theme.liquid before </body> in place of the widget's own script tag, and replace CHAT_SCRIPT_URL with your widget's script URL (including your widget key):
<button type="button" id="chat-launcher">Chat with us</button>
<script>
function loadChat(open) {
if (window.chatLoaded) return; window.chatLoaded = true;
var s = document.createElement('script');
s.async = true; s.src = 'CHAT_SCRIPT_URL';
document.head.appendChild(s).onload = function () { document.getElementById('chat-launcher').remove(); if (open) { /* your widget's open call */ } };
}
function chatIfAllowed() { var p = window.Shopify.customerPrivacy; if (p && p.analyticsProcessingAllowed()) loadChat(false); }
document.getElementById('chat-launcher').addEventListener('click', function () { loadChat(true); });
window.Shopify.loadFeatures([{ name: 'consent-tracking-api', version: '0.1' }], chatIfAllowed);
document.addEventListener('visitorConsentCollected', chatIfAllowed);
</script>
Style the button like your chat bubble. Replace the comment with your widget's open method (every chat tool documents one), so one click both loads and opens the chat. If your policy lists the chat under another purpose, swap analyticsProcessingAllowed for preferencesProcessingAllowed or marketingAllowed.
Leak 3: an analytics script was hard-coded in the theme head
Problem. An Ahrefs-type web analytics script sat directly in the theme's head, so it ran for everyone.
How we found it. In the theme code editor, search the layout file and snippets for <script src= pointing to third-party domains.
Fix. The script is now injected only after analytics consent.
Result. Not loaded before a choice or after Decline.
Do it yourself. Paste this in layout/theme.liquid before </head>, in place of the old <script src=…> tag, and replace the URL with your script's:
<script>
function loadVendorIfAllowed() {
var privacy = window.Shopify.customerPrivacy;
if (window.vendorLoaded || !privacy || !privacy.analyticsProcessingAllowed()) return;
window.vendorLoaded = true;
var s = document.createElement('script');
s.async = true; s.src = 'https://vendor.example/script.js';
document.head.appendChild(s);
}
window.Shopify.loadFeatures([{ name: 'consent-tracking-api', version: '0.1' }], loadVendorIfAllowed);
document.addEventListener('visitorConsentCollected', loadVendorIfAllowed);
</script>
It checks consent on page load and again when the visitor makes a choice, and loads the script once. For an ad tool, swap analyticsProcessingAllowed for marketingAllowed. Use a different vendorLoaded name for each script.
Names from Shopify's Customer Privacy API (opens in a new tab). The docs show version: '0.2', but on the theme stores we checked, Shopify's own banner had already loaded 0.1 and a 0.2 request returned an error. That's why the snippet reads Shopify.customerPrivacy directly instead of trusting the callback. The same pattern went live on a client store this week.
Leak 4: fonts came in through the email popup, not the theme
Problem. The theme already self-hosted its font. But the email sign-up tool (Klaviyo-type) injected Google Fonts and an Adobe Fonts kit on every page, before consent. A back-in-stock modal also imported a Google font when it opened.
How we found it. Filter Network for fonts.googleapis, fonts.gstatic and typekit. Open the popup and the back-in-stock modal too.
Fix. The font files went onto the shop's own CDN, and the popup tool was switched to that copy.
Result. Zero third-party font requests before consent.
Fonts have been tested in court at least once. On 20 January 2022 the Munich Regional Court ruled that a site loading Google Fonts from Google's servers without consent had passed the visitor's IP address to Google, and awarded €100 in damages. That is one court's view, not EU law.
Leak 5: an old ad call broke a form
Problem. A theme contact form still called another region's Google Ads account. With consent declined, gtag isn't defined, so the call threw an error and the form code after it never ran.
How we found it. Search the theme for gtag( and conversion, then submit the form with the console open.
Fix. The call was removed.
Result. The form code runs again, with no console error.
None of these five needed a new app. Each was one change in the theme or in the popup tool's settings.
What already worked: Consent Mode, checkout and app pixels
The Google tags were never the problem. The store runs Google Consent Mode v2 in basic mode, and it behaved as documented: no Google tags before a choice or after Decline, and after Accept all four signals granted (ad_storage, analytics_storage, ad_user_data, ad_personalization).
Basic isn't the only option. Advanced mode loads the tags with defaults set to denied and sends cookieless pings after Decline. Google uses those pings for an advertiser-specific model instead of a general one. Which mode you run is a legal and business decision, not a technical one.
Checkout followed the same choice. A buyer who declined on the storefront sent no purchase hit to Google.
Two app pixels declared no privacy permission, so Shopify couldn't gate them. They gated themselves and stayed silent until Accept. You only know that by testing.
Run the same check on your store in 10 minutes
Set up once: a fresh private window, an EU location or VPN, and dev tools open on Network (Preserve log on, cache off) and Application (Cookies, Local storage). Use a product page with video and reviews, not only the homepage. Run the list three times: no choice, Decline, Accept.
- Video embeds. Filter
youtube|doubleclick. Pass: no requests and no youtube.com cookies before Accept. - Chat widget. Filter the vendor's domain, check Local storage. Pass: nothing until the launcher is clicked or consent is given.
- Theme scripts. Search the layout and snippets for third-party
<script src=. Pass: each one waits for the right consent. - Fonts. Filter
fonts.googleapis,fonts.gstatic,typekit; open popups too. Pass: zero third-party font requests before consent. - Old ad tags. Search for
gtag(andconversion. Pass: no calls to accounts you don't run, no console errors on forms. - Consent Mode. Google Tag Assistant, Consent tab. Pass (basic): no Google tags before a choice or after Decline; after Accept, the On-page Update column shows all four signals granted.
- Pixels. Settings > Customer events. Pass: every app and custom pixel requires permission (custom pixels: Permission set to Required), or you've tested that it stays silent after Decline.
- Your policy. Pass: everything that still loads after Decline is strictly necessary (store, cart, checkout, payment, security), and your privacy policy lists each one with its purpose.
A banner that looks right proves nothing
Test it, don't trust it. Run the eight checks in all three states: no choice, Decline, Accept.
Fix one leak at a time, then rerun the checks, so you know which change worked.
After Decline, only what the store needs should load: store, cart, checkout, payment, security. Anything else you're unsure about goes to your lawyer.
Sources
- EDPB, Guidelines 2/2023 on the Technical Scope of Art. 5(3) of the ePrivacy Directive, Version 2.0 (opens in a new tab) (final version after public consultation, October 2024)
- YouTube Help, Embed videos & playlists (opens in a new tab) (privacy-enhanced mode)
- Google Ads Help, About consent mode (opens in a new tab)
- Google for Developers, Consent mode overview (opens in a new tab)
- Shopify.dev, Customer Privacy API (opens in a new tab)
- Shopify Help Center, Privacy settings (opens in a new tab)
- Google for Developers, Troubleshoot consent mode with Tag Assistant (opens in a new tab)
- Shopify Help Center, Manage your custom pixels (opens in a new tab)
- Landgericht München I, judgment of 20 January 2022, 3 O 17493/20 (Google Fonts), summarised by IHK (opens in a new tab)
- Our own test: one live EU Shopify store, three browser profiles, October 2026 (anonymised)