Read full summary
Complete guide to Shopify cookie consent: app audit methodology, theme integration, checkout considerations, and vetting apps for privacy compliance. Protect your store from liability caused by third-party apps.
*Summary by Claude AI*
---
title: "Shopify Cookie Consent Guide 2025: Complete Implementation Tutorial"
description: "Master Shopify cookie consent with this comprehensive guide. Learn Customer Privacy API, Checkout Extensibility, Web Pixels, and multi-region compliance strategies."
keywords: ["shopify cookie consent", "shopify gdpr compliance", "customer privacy api", "shopify cmp", "shopify privacy banner", "checkout extensibility consent", "shopify web pixels"]
author: "GetCookies Privacy Team"
date: "2025-01-15"
category: "Platform Guides"
featured: true
readingTime: "18 min read"
---
## Does Shopify handle cookie consent automatically?
Shopify provides a basic "Customer Privacy" banner app, but for advanced needs (multi-region, custom design, GTM integration), you need a third-party CMP that integrates with Shopify's **Customer Privacy API**. Shopify's native solution covers basic use cases, but businesses selling in multiple jurisdictions (EU, California, Brazil, etc.) need sophisticated consent orchestration that the built-in tools don't provide.
## Introduction: The E-Commerce Privacy Landscape
E-commerce stores face unique privacy challenges. Unlike content websites where users browse passively, online stores collect payment information, shipping addresses, purchase history, and behavioral data that creates detailed consumer profiles. This makes consent management not just a legal requirement but a trust-building exercise.
Shopify powers over 4 million online stores globally. As the platform of choice for DTC brands, understanding its privacy architecture is essential for any modern e-commerce operator. This guide provides everything you need to implement compliant cookie consent on Shopify in 2025—from basic banner setup to advanced headless commerce implementations.
### Why Shopify Privacy Requires Special Attention
Several factors make Shopify privacy implementation distinct in 2025:
| Factor | Challenge | Solution |
|--------|-----------|----------|
| **Theme Liquid Templates** | Legacy implementations often bypass consent | Migrate to Web Pixels or App Blocks |
| **App Ecosystem** | Third-party apps inject their own tracking | Audit app permissions regularly |
| **Checkout Flow** | Limited customization on non-Plus plans | Use Checkout Extensibility (Plus) |
| **Web Pixels** | Now the standard for tracking; sandboxed | Configure pixel consent settings |
| **Markets Pro** | Multi-region selling requires geo-detection | Implement region-specific banners |
## Understanding Shopify's Privacy Architecture
### The Three Layers of Shopify Tracking
Shopify's tracking operates across three distinct layers, each requiring different consent handling:
```typescript
// shopify-privacy-architecture.ts
interface ShopifyPrivacyLayers {
// Layer 1: Platform Analytics (Shopify's own tracking)
platformAnalytics: {
enabled: boolean;
dataCollected: [
'page_views',
'add_to_cart',
'checkout_started',
'purchase_completed',
'search_queries'
];
consentRequired: 'analytics';
};
// Layer 2: Third-Party Apps
thirdPartyApps: {
apps: AppPrivacyProfile[];
permissionsGranted: string[];
dataSharing: 'restricted' | 'full';
consentRequired: 'marketing' | 'analytics';
};
// Layer 3: Custom Theme Scripts
customScripts: {
location: 'theme.liquid' | 'checkout' | 'app_block';
controlled: boolean;
consentRequired: string[];
};
}
interface AppPrivacyProfile {
appId: string;
appName: string;
developer: string;
dataAccessed: string[];
externalServices: string[];
privacyPolicyUrl: string;
gdprCompliant: boolean;
ccpaCompliant: boolean;
}
// Example: Audit installed apps for privacy
class ShopifyAppPrivacyAuditor {
private apps: AppPrivacyProfile[] = [];
async auditInstalledApps(): Promise {
const report: PrivacyAuditReport = {
totalApps: this.apps.length,
highRiskApps: [],
dataCategories: new Set(),
externalDataSharing: [],
recommendations: []
};
for (const app of this.apps) {
// Check for high-risk data access
const highRiskData = [
'customer_email',
'customer_phone',
'payment_info',
'browsing_history'
];
const accessesHighRisk = app.dataAccessed.some(
d => highRiskData.includes(d)
);
if (accessesHighRisk) {
report.highRiskApps.push({
app: app.appName,
riskFactors: app.dataAccessed.filter(d => highRiskData.includes(d)),
externalSharing: app.externalServices.length > 0
});
}
// Track all data categories
app.dataAccessed.forEach(d => report.dataCategories.add(d));
// Track external data sharing
if (app.externalServices.length > 0) {
report.externalDataSharing.push({
app: app.appName,
services: app.externalServices
});
}
}
// Generate recommendations
if (report.highRiskApps.length > 0) {
report.recommendations.push(
'Review high-risk apps and ensure DPA agreements are in place'
);
}
if (report.externalDataSharing.length > 5) {
report.recommendations.push(
'Consider consolidating apps to reduce third-party data sharing'
);
}
return report;
}
}
interface PrivacyAuditReport {
totalApps: number;
highRiskApps: Array<{
app: string;
riskFactors: string[];
externalSharing: boolean;
}>;
dataCategories: Set;
externalDataSharing: Array<{
app: string;
services: string[];
}>;
recommendations: string[];
}
```
## The Customer Privacy API Deep Dive
Shopify's Customer Privacy API is the foundation of consent management on the platform. Understanding it thoroughly is essential for any custom implementation.
### Loading the Privacy API
```javascript
// customer-privacy-api-setup.js
// Method 1: Feature detection and loading
function initializeCustomerPrivacy() {
if (window.Shopify && window.Shopify.loadFeatures) {
window.Shopify.loadFeatures([
{
name: 'consent-tracking-api',
version: '0.1',
}
], function(error) {
if (error) {
console.error('Failed to load Customer Privacy API:', error);
// Fallback: Block all tracking by default
blockAllTracking();
return;
}
// API is ready
onPrivacyAPIReady();
});
} else {
// Shopify object not available (possibly headless)
console.warn('Shopify Customer Privacy API not available');
initializeHeadlessConsent();
}
}
function onPrivacyAPIReady() {
const customerPrivacy = window.Shopify.customerPrivacy;
// Check current consent state
const currentConsent = {
analytics: customerPrivacy.analyticsProcessingAllowed(),
marketing: customerPrivacy.marketingAllowed(),
preferences: customerPrivacy.preferencesProcessingAllowed(),
saleOfData: customerPrivacy.saleOfDataAllowed(),
// GDPR-specific
userCanBeTracked: customerPrivacy.userCanBeTracked(),
// Region detection
shouldShowBanner: customerPrivacy.shouldShowBanner(),
// Check for existing consent
hasConsent: customerPrivacy.getTrackingConsent() !== 'no_interaction'
};
console.log('Current consent state:', currentConsent);
// Subscribe to consent changes
customerPrivacy.subscribe('visitorConsentCollected', (event) => {
console.log('Consent collected:', event);
handleConsentChange(event);
});
// Show banner if needed
if (currentConsent.shouldShowBanner && !currentConsent.hasConsent) {
showConsentBanner();
}
}
// Method 2: Using Shopify's built-in privacy banner customization
function customizeNativeBanner() {
// Access privacy banner settings (if using Shopify's native banner)
if (window.Shopify.customerPrivacy) {
// Native banner customization is limited
// For full control, implement custom banner
}
}
```
### Setting Consent States
```typescript
// consent-state-manager.ts
interface ShopifyConsentState {
analytics: boolean;
marketing: boolean;
preferences: boolean;
saleOfData: boolean;
}
interface ConsentUpdateOptions {
headlessMode?: boolean;
persistToServer?: boolean;
syncWithPixels?: boolean;
}
class ShopifyConsentManager {
private customerPrivacy: any;
private consentState: ShopifyConsentState;
private eventListeners: Map = new Map();
constructor() {
this.consentState = {
analytics: false,
marketing: false,
preferences: false,
saleOfData: false
};
}
async initialize(): Promise {
return new Promise((resolve, reject) => {
if (!window.Shopify?.loadFeatures) {
reject(new Error('Shopify not available'));
return;
}
window.Shopify.loadFeatures([
{ name: 'consent-tracking-api', version: '0.1' }
], (error) => {
if (error) {
reject(error);
return;
}
this.customerPrivacy = window.Shopify.customerPrivacy;
this.loadCurrentState();
this.setupEventListeners();
resolve();
});
});
}
private loadCurrentState(): void {
this.consentState = {
analytics: this.customerPrivacy.analyticsProcessingAllowed(),
marketing: this.customerPrivacy.marketingAllowed(),
preferences: this.customerPrivacy.preferencesProcessingAllowed(),
saleOfData: this.customerPrivacy.saleOfDataAllowed()
};
}
private setupEventListeners(): void {
this.customerPrivacy.subscribe(
'visitorConsentCollected',
this.handleConsentCollected.bind(this)
);
}
private handleConsentCollected(event: any): void {
this.loadCurrentState();
this.emit('consentChanged', this.consentState);
// Sync with Google Tag Manager dataLayer
this.syncWithGTM();
// Update Web Pixels
this.syncWithWebPixels();
}
// Accept all cookies
acceptAll(options: ConsentUpdateOptions = {}): void {
this.setConsent({
analytics: true,
marketing: true,
preferences: true,
saleOfData: true
}, options);
}
// Reject all (essential only)
rejectAll(options: ConsentUpdateOptions = {}): void {
this.setConsent({
analytics: false,
marketing: false,
preferences: false,
saleOfData: false
}, options);
}
// Granular consent setting
setConsent(
consent: Partial,
options: ConsentUpdateOptions = {}
): void {
const newState = { ...this.consentState, ...consent };
// Use Shopify's API
this.customerPrivacy.setTrackingConsent(
newState.marketing || newState.analytics,
() => {
console.log('Tracking consent updated');
this.consentState = newState;
// Additional consent signals for CCPA
if ('saleOfData' in consent) {
this.setSaleOfDataConsent(consent.saleOfData!);
}
this.emit('consentChanged', newState);
}
);
// Persist to server if needed (for logged-in users)
if (options.persistToServer) {
this.persistConsentToServer(newState);
}
}
private setSaleOfDataConsent(allowed: boolean): void {
// CCPA-specific: "Do Not Sell My Personal Information"
if (this.customerPrivacy.setSaleOfDataAllowed) {
this.customerPrivacy.setSaleOfDataAllowed(allowed);
}
// Also set GPC signal
if (!allowed && navigator.globalPrivacyControl === undefined) {
// Respect user's choice even without GPC
this.setGPCRespectingState();
}
}
private setGPCRespectingState(): void {
// Block sale of data when GPC is detected
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'gpc_signal_detected',
gpc_value: true,
sale_of_data: false
});
}
private syncWithGTM(): void {
window.dataLayer = window.dataLayer || [];
// Push consent update to GTM
window.dataLayer.push({
event: 'consent_update',
consent_state: {
ad_storage: this.consentState.marketing ? 'granted' : 'denied',
analytics_storage: this.consentState.analytics ? 'granted' : 'denied',
functionality_storage: this.consentState.preferences ? 'granted' : 'denied',
personalization_storage: this.consentState.preferences ? 'granted' : 'denied',
security_storage: 'granted' // Always allowed
}
});
// Google Consent Mode v2 update
if (typeof gtag === 'function') {
gtag('consent', 'update', {
ad_storage: this.consentState.marketing ? 'granted' : 'denied',
ad_user_data: this.consentState.marketing ? 'granted' : 'denied',
ad_personalization: this.consentState.marketing ? 'granted' : 'denied',
analytics_storage: this.consentState.analytics ? 'granted' : 'denied'
});
}
}
private syncWithWebPixels(): void {
// Web Pixels automatically respect Customer Privacy API
// But we can send additional context
if (window.Shopify?.analytics?.publish) {
window.Shopify.analytics.publish('consent_updated', {
analytics: this.consentState.analytics,
marketing: this.consentState.marketing
});
}
}
private async persistConsentToServer(state: ShopifyConsentState): Promise {
try {
const response = await fetch('/apps/privacy/consent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': this.getAccessToken()
},
body: JSON.stringify({
consent: state,
timestamp: new Date().toISOString(),
source: 'customer_privacy_banner'
})
});
if (!response.ok) {
throw new Error('Failed to persist consent');
}
} catch (error) {
console.error('Error persisting consent:', error);
}
}
private getAccessToken(): string {
// Get from meta tag or Shopify global
return document.querySelector('meta[name="shopify-access-token"]')
?.getAttribute('content') || '';
}
// Event emitter pattern
on(event: string, callback: Function): void {
if (!this.eventListeners.has(event)) {
this.eventListeners.set(event, []);
}
this.eventListeners.get(event)!.push(callback);
}
private emit(event: string, data: any): void {
const listeners = this.eventListeners.get(event) || [];
listeners.forEach(callback => callback(data));
}
// Get current state
getConsentState(): ShopifyConsentState {
return { ...this.consentState };
}
// Check if user should see banner
shouldShowBanner(): boolean {
return this.customerPrivacy?.shouldShowBanner() ?? true;
}
// Get user's region for compliance
getUserRegion(): string {
return this.customerPrivacy?.getRegion?.() || 'unknown';
}
}
// Usage
const consentManager = new ShopifyConsentManager();
await consentManager.initialize();
// Listen for changes
consentManager.on('consentChanged', (state) => {
console.log('Consent updated:', state);
});
// Set consent based on user choice
document.getElementById('accept-all')?.addEventListener('click', () => {
consentManager.acceptAll();
});
document.getElementById('reject-all')?.addEventListener('click', () => {
consentManager.rejectAll();
});
```
## Web Pixels and Consent
Shopify's Web Pixels run in a sandboxed environment, which has important implications for consent management.
### Understanding the Web Pixel Sandbox
```typescript
// web-pixel-consent.ts
interface WebPixelConsentConfig {
pixelId: string;
runtimeContext: 'sandbox' | 'lax' | 'strict';
consentRequired: ('analytics' | 'marketing')[];
dataCollected: string[];
}
// Web Pixel extension configuration (in pixel settings)
const pixelConsentConfig: WebPixelConsentConfig = {
pixelId: 'my-custom-pixel',
runtimeContext: 'sandbox',
consentRequired: ['marketing', 'analytics'],
dataCollected: [
'page_viewed',
'product_viewed',
'collection_viewed',
'cart_viewed',
'checkout_started',
'checkout_completed',
'payment_info_submitted'
]
};
// Inside your Web Pixel extension (runs in sandbox)
// web-pixel-extension.ts
import { register } from '@shopify/web-pixels-extension';
register(({ analytics, browser, settings, init }) => {
// Check consent before tracking
const hasConsent = init.customerPrivacy?.analyticsProcessingAllowed;
if (!hasConsent) {
console.log('Analytics consent not granted, pixel inactive');
return;
}
// Subscribe to standard events
analytics.subscribe('page_viewed', (event) => {
if (!checkCurrentConsent()) return;
sendToAnalytics({
type: 'pageview',
url: event.context.document.location.href,
title: event.context.document.title,
referrer: event.context.document.referrer
});
});
analytics.subscribe('product_viewed', (event) => {
if (!checkCurrentConsent()) return;
const product = event.data.productVariant;
sendToAnalytics({
type: 'product_view',
product_id: product.product.id,
variant_id: product.id,
title: product.product.title,
price: product.price.amount,
currency: product.price.currencyCode
});
});
analytics.subscribe('checkout_completed', (event) => {
if (!checkCurrentConsent()) return;
const checkout = event.data.checkout;
sendToAnalytics({
type: 'purchase',
order_id: checkout.order?.id,
total: checkout.totalPrice.amount,
currency: checkout.currencyCode,
items: checkout.lineItems.map(item => ({
id: item.variant?.product.id,
quantity: item.quantity,
price: item.variant?.price.amount
}))
});
});
// Helper to check current consent state
function checkCurrentConsent(): boolean {
// In sandbox, consent state is passed at initialization
// For real-time updates, listen to consent changes
return init.customerPrivacy?.analyticsProcessingAllowed ?? false;
}
// Send data to your analytics endpoint
async function sendToAnalytics(data: any): Promise {
try {
await browser.sendBeacon(settings.analyticsEndpoint, {
...data,
timestamp: new Date().toISOString(),
shop: init.context.shop.domain
});
} catch (error) {
console.error('Failed to send analytics:', error);
}
}
});
```
### Configuring Pixel Consent Requirements
```typescript
// pixel-consent-configuration.ts
interface PixelConfiguration {
id: string;
name: string;
consentCategories: ConsentCategory[];
geoRestrictions: GeoRestriction[];
dataProcessing: DataProcessingConfig;
}
interface ConsentCategory {
category: 'necessary' | 'analytics' | 'marketing' | 'preferences';
required: boolean;
description: string;
}
interface GeoRestriction {
region: string; // ISO country code or 'EU', 'EEA', etc.
consentRequired: boolean;
legitimateInterestAllowed: boolean;
}
interface DataProcessingConfig {
purposes: string[];
retention: string;
thirdPartySharing: boolean;
crossBorderTransfer: boolean;
}
// Example configurations for common pixels
const pixelConfigurations: Record = {
'meta-pixel': {
id: 'meta-pixel',
name: 'Meta (Facebook) Pixel',
consentCategories: [
{
category: 'marketing',
required: true,
description: 'Required for ad tracking and retargeting'
},
{
category: 'analytics',
required: false,
description: 'Optional for conversion analytics'
}
],
geoRestrictions: [
{ region: 'EU', consentRequired: true, legitimateInterestAllowed: false },
{ region: 'US-CA', consentRequired: true, legitimateInterestAllowed: false },
{ region: 'US', consentRequired: false, legitimateInterestAllowed: true }
],
dataProcessing: {
purposes: ['advertising', 'analytics', 'personalization'],
retention: '180 days',
thirdPartySharing: true,
crossBorderTransfer: true
}
},
'google-analytics': {
id: 'google-analytics',
name: 'Google Analytics 4',
consentCategories: [
{
category: 'analytics',
required: true,
description: 'Required for website analytics'
}
],
geoRestrictions: [
{ region: 'EU', consentRequired: true, legitimateInterestAllowed: false },
{ region: 'US', consentRequired: false, legitimateInterestAllowed: true }
],
dataProcessing: {
purposes: ['analytics', 'measurement'],
retention: '14 months max',
thirdPartySharing: true,
crossBorderTransfer: true
}
},
'klaviyo': {
id: 'klaviyo',
name: 'Klaviyo',
consentCategories: [
{
category: 'marketing',
required: true,
description: 'Required for email marketing'
},
{
category: 'analytics',
required: false,
description: 'Optional for behavior tracking'
}
],
geoRestrictions: [
{ region: 'EU', consentRequired: true, legitimateInterestAllowed: false },
{ region: 'US', consentRequired: false, legitimateInterestAllowed: true }
],
dataProcessing: {
purposes: ['email_marketing', 'customer_segmentation', 'personalization'],
retention: 'Until unsubscribe',
thirdPartySharing: false,
crossBorderTransfer: true
}
}
};
// Pixel consent orchestrator
class PixelConsentOrchestrator {
private configurations: Map = new Map();
private userRegion: string = 'unknown';
private consentState: Record = {};
constructor() {
Object.entries(pixelConfigurations).forEach(([id, config]) => {
this.configurations.set(id, config);
});
}
setUserRegion(region: string): void {
this.userRegion = region;
}
updateConsent(category: string, granted: boolean): void {
this.consentState[category] = granted;
this.evaluatePixels();
}
private evaluatePixels(): void {
this.configurations.forEach((config, pixelId) => {
const shouldActivate = this.shouldActivatePixel(config);
if (shouldActivate) {
this.activatePixel(pixelId);
} else {
this.deactivatePixel(pixelId);
}
});
}
private shouldActivatePixel(config: PixelConfiguration): boolean {
// Check geo restrictions
const geoRule = config.geoRestrictions.find(
r => this.matchesRegion(r.region)
);
if (!geoRule) {
// No specific rule, default to most restrictive
return this.hasAllRequiredConsent(config);
}
if (!geoRule.consentRequired && geoRule.legitimateInterestAllowed) {
// Can use legitimate interest
return true;
}
// Must have consent
return this.hasAllRequiredConsent(config);
}
private hasAllRequiredConsent(config: PixelConfiguration): boolean {
return config.consentCategories
.filter(c => c.required)
.every(c => this.consentState[c.category] === true);
}
private matchesRegion(region: string): boolean {
const regionMappings: Record = {
'EU': ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR',
'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL',
'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE'],
'EEA': ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR',
'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL',
'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'IS', 'LI', 'NO'],
'US-CA': ['US-CA'],
'US': ['US']
};
if (regionMappings[region]) {
return regionMappings[region].includes(this.userRegion);
}
return this.userRegion === region;
}
private activatePixel(pixelId: string): void {
console.log(`Activating pixel: ${pixelId}`);
// Implementation depends on pixel type
window.dispatchEvent(new CustomEvent('pixel:activate', {
detail: { pixelId }
}));
}
private deactivatePixel(pixelId: string): void {
console.log(`Deactivating pixel: ${pixelId}`);
window.dispatchEvent(new CustomEvent('pixel:deactivate', {
detail: { pixelId }
}));
}
}
```
## Checkout Extensibility and Consent
For Shopify Plus merchants, Checkout Extensibility provides deep customization options, including consent management during the purchase flow.
### Checkout UI Extension for Consent
```typescript
// checkout-consent-extension.tsx
import {
reactExtension,
Banner,
BlockStack,
Checkbox,
Text,
Link,
useExtensionCapability,
useBuyerJourneyIntercept,
useApplyMetafieldsChange,
useMetafield
} from '@shopify/ui-extensions-react/checkout';
export default reactExtension(
'purchase.checkout.block.render',
() =>
);
function CheckoutConsentExtension() {
const canBlockProgress = useExtensionCapability('block_progress');
const applyMetafieldsChange = useApplyMetafieldsChange();
// Store consent in customer metafield
const marketingConsent = useMetafield({
namespace: 'privacy',
key: 'marketing_consent'
});
const [emailConsent, setEmailConsent] = useState(false);
const [smsConsent, setSmsConsent] = useState(false);
const [thirdPartyConsent, setThirdPartyConsent] = useState(false);
// Block checkout if required consent not given
useBuyerJourneyIntercept(({ canBlockProgress }) => {
// In regions requiring consent, block if not acknowledged
if (canBlockProgress && requiresExplicitConsent()) {
// Allow to proceed, consent is optional but recorded
return { behavior: 'allow' };
}
return { behavior: 'allow' };
});
const handleConsentChange = async (
type: 'email' | 'sms' | 'third_party',
value: boolean
) => {
switch (type) {
case 'email':
setEmailConsent(value);
break;
case 'sms':
setSmsConsent(value);
break;
case 'third_party':
setThirdPartyConsent(value);
break;
}
// Store in metafield for post-purchase processing
await applyMetafieldsChange({
type: 'updateMetafield',
namespace: 'privacy',
key: `${type}_consent`,
valueType: 'boolean',
value: value.toString()
});
};
return (
handleConsentChange('email', checked)}
>
handleConsentChange('sms', checked)}
>
handleConsentChange('third_party', checked)}
>
{requiresExplicitConsent() && (
Based on your location, explicit consent is required for marketing communications.
)}
);
}
function requiresExplicitConsent(): boolean {
// Check buyer's region from extension context
// Implementation would use useShippingAddress or similar
return true; // Default to requiring consent
}
```
### Post-Purchase Consent Verification
```typescript
// post-purchase-consent-webhook.ts
import { shopifyApi } from '@shopify/shopify-api';
interface OrderWebhook {
id: string;
customer: {
id: string;
email: string;
phone?: string;
metafields?: Array<{
namespace: string;
key: string;
value: string;
}>;
};
shipping_address: {
country_code: string;
province_code?: string;
};
}
interface ConsentRecord {
customerId: string;
email: string;
orderId: string;
timestamp: string;
region: string;
consents: {
email_marketing: boolean;
sms_marketing: boolean;
third_party_sharing: boolean;
};
legalBasis: 'consent' | 'legitimate_interest';
}
async function handleOrderCreated(order: OrderWebhook): Promise {
const consentRecord = await extractConsentFromOrder(order);
// Store consent record for compliance
await storeConsentRecord(consentRecord);
// Sync with marketing platforms based on consent
await syncMarketingConsent(consentRecord);
// Update customer tags for segmentation
await updateCustomerTags(order.customer.id, consentRecord);
}
async function extractConsentFromOrder(order: OrderWebhook): Promise {
const metafields = order.customer.metafields || [];
const getMetafieldValue = (key: string): boolean => {
const field = metafields.find(
m => m.namespace === 'privacy' && m.key === `${key}_consent`
);
return field?.value === 'true';
};
const region = `${order.shipping_address.country_code}${
order.shipping_address.province_code
? `-${order.shipping_address.province_code}`
: ''
}`;
// Determine legal basis based on region
const requiresConsent = isConsentRequiredRegion(region);
return {
customerId: order.customer.id,
email: order.customer.email,
orderId: order.id,
timestamp: new Date().toISOString(),
region,
consents: {
email_marketing: getMetafieldValue('email'),
sms_marketing: getMetafieldValue('sms'),
third_party_sharing: getMetafieldValue('third_party')
},
legalBasis: requiresConsent ? 'consent' : 'legitimate_interest'
};
}
function isConsentRequiredRegion(region: string): boolean {
const consentRequiredRegions = [
// EU countries
'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR',
'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL',
'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE',
// EEA
'IS', 'LI', 'NO',
// UK
'GB',
// California
'US-CA',
// Canada
'CA',
// Brazil
'BR'
];
const countryCode = region.split('-')[0];
return consentRequiredRegions.includes(countryCode) ||
consentRequiredRegions.includes(region);
}
async function syncMarketingConsent(record: ConsentRecord): Promise {
// Sync with Klaviyo
if (record.consents.email_marketing) {
await syncKlaviyoConsent(record);
}
// Sync with SMS provider (e.g., Attentive, Postscript)
if (record.consents.sms_marketing) {
await syncSMSConsent(record);
}
// Update Meta CAPI consent
if (record.consents.third_party_sharing) {
await syncMetaCAPIConsent(record);
}
}
async function syncKlaviyoConsent(record: ConsentRecord): Promise {
const response = await fetch('https://a.klaviyo.com/api/profile-subscription-bulk-create-jobs/', {
method: 'POST',
headers: {
'Authorization': `Klaviyo-API-Key ${process.env.KLAVIYO_API_KEY}`,
'Content-Type': 'application/json',
'revision': '2024-02-15'
},
body: JSON.stringify({
data: {
type: 'profile-subscription-bulk-create-job',
attributes: {
profiles: {
data: [{
type: 'profile',
attributes: {
email: record.email,
subscriptions: {
email: {
marketing: {
consent: 'SUBSCRIBED',
consent_timestamp: record.timestamp
}
}
}
}
}]
}
},
relationships: {
list: {
data: {
type: 'list',
id: process.env.KLAVIYO_LIST_ID
}
}
}
}
})
});
if (!response.ok) {
console.error('Failed to sync Klaviyo consent:', await response.text());
}
}
```
## Multi-Region Compliance with Shopify Markets
Shopify Markets enables selling to multiple countries with localized experiences. This includes region-specific privacy compliance.
### Geo-Aware Consent Banners
```typescript
// geo-aware-consent-banner.ts
interface RegionPrivacyConfig {
region: string;
displayName: string;
regulations: string[];
bannerConfig: BannerConfig;
consentModel: 'opt-in' | 'opt-out' | 'notice-only';
defaultState: Record;
}
interface BannerConfig {
showOnLoad: boolean;
blockScripts: boolean;
position: 'bottom' | 'top' | 'center';
style: 'bar' | 'modal' | 'popup';
categories: ConsentCategory[];
translations: Record;
}
interface BannerTranslations {
title: string;
description: string;
acceptAll: string;
rejectAll: string;
customize: string;
privacyPolicy: string;
categories: Record;
}
interface CategoryTranslation {
name: string;
description: string;
}
// Regional configurations
const regionConfigs: RegionPrivacyConfig[] = [
{
region: 'EU',
displayName: 'European Union',
regulations: ['GDPR', 'ePrivacy'],
bannerConfig: {
showOnLoad: true,
blockScripts: true,
position: 'bottom',
style: 'modal',
categories: [
{ id: 'necessary', name: 'Necessary', required: true },
{ id: 'analytics', name: 'Analytics', required: false },
{ id: 'marketing', name: 'Marketing', required: false },
{ id: 'preferences', name: 'Preferences', required: false }
],
translations: {
'en': {
title: 'We value your privacy',
description: 'We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic. By clicking "Accept All", you consent to our use of cookies.',
acceptAll: 'Accept All',
rejectAll: 'Reject All',
customize: 'Customize',
privacyPolicy: 'Privacy Policy',
categories: {
necessary: {
name: 'Strictly Necessary',
description: 'These cookies are essential for the website to function properly.'
},
analytics: {
name: 'Analytics',
description: 'Help us understand how visitors interact with our website.'
},
marketing: {
name: 'Marketing',
description: 'Used to deliver relevant advertisements and track campaign performance.'
},
preferences: {
name: 'Preferences',
description: 'Remember your settings and preferences for a better experience.'
}
}
},
'de': {
title: 'Wir schätzen Ihre Privatsphäre',
description: 'Wir verwenden Cookies, um Ihr Surferlebnis zu verbessern, personalisierte Inhalte bereitzustellen und unseren Traffic zu analysieren.',
acceptAll: 'Alle akzeptieren',
rejectAll: 'Alle ablehnen',
customize: 'Anpassen',
privacyPolicy: 'Datenschutzerklärung',
categories: {
necessary: {
name: 'Unbedingt erforderlich',
description: 'Diese Cookies sind für die ordnungsgemäße Funktion der Website unerlässlich.'
},
analytics: {
name: 'Analytik',
description: 'Helfen uns zu verstehen, wie Besucher mit unserer Website interagieren.'
},
marketing: {
name: 'Marketing',
description: 'Werden verwendet, um relevante Werbung zu liefern und die Kampagnenleistung zu verfolgen.'
},
preferences: {
name: 'Präferenzen',
description: 'Merken Sie sich Ihre Einstellungen und Präferenzen für ein besseres Erlebnis.'
}
}
},
'fr': {
title: 'Nous respectons votre vie privée',
description: 'Nous utilisons des cookies pour améliorer votre expérience de navigation, diffuser du contenu personnalisé et analyser notre trafic.',
acceptAll: 'Tout accepter',
rejectAll: 'Tout refuser',
customize: 'Personnaliser',
privacyPolicy: 'Politique de confidentialité',
categories: {
necessary: {
name: 'Strictement nécessaires',
description: 'Ces cookies sont essentiels au bon fonctionnement du site.'
},
analytics: {
name: 'Analytiques',
description: 'Nous aident à comprendre comment les visiteurs interagissent avec notre site.'
},
marketing: {
name: 'Marketing',
description: 'Utilisés pour diffuser des publicités pertinentes et suivre les performances des campagnes.'
},
preferences: {
name: 'Préférences',
description: 'Mémorisent vos paramètres et préférences pour une meilleure expérience.'
}
}
}
}
},
consentModel: 'opt-in',
defaultState: {
necessary: true,
analytics: false,
marketing: false,
preferences: false
}
},
{
region: 'US-CA',
displayName: 'California',
regulations: ['CCPA', 'CPRA'],
bannerConfig: {
showOnLoad: true,
blockScripts: false,
position: 'bottom',
style: 'bar',
categories: [
{ id: 'necessary', name: 'Necessary', required: true },
{ id: 'analytics', name: 'Analytics', required: false },
{ id: 'sale_sharing', name: 'Sale/Sharing', required: false }
],
translations: {
'en': {
title: 'Your Privacy Choices',
description: 'We use cookies and may share your information with partners. You have the right to opt out of the sale or sharing of your personal information.',
acceptAll: 'Accept All',
rejectAll: 'Do Not Sell or Share',
customize: 'Privacy Settings',
privacyPolicy: 'Privacy Policy',
categories: {
necessary: {
name: 'Essential',
description: 'Required for the website to function.'
},
analytics: {
name: 'Analytics',
description: 'Help us improve our website.'
},
sale_sharing: {
name: 'Sale/Sharing of Data',
description: 'Allow sharing of personal information with partners for targeted advertising.'
}
}
}
}
},
consentModel: 'opt-out',
defaultState: {
necessary: true,
analytics: true,
sale_sharing: true
}
},
{
region: 'BR',
displayName: 'Brazil',
regulations: ['LGPD'],
bannerConfig: {
showOnLoad: true,
blockScripts: true,
position: 'bottom',
style: 'modal',
categories: [
{ id: 'necessary', name: 'Necessário', required: true },
{ id: 'analytics', name: 'Análise', required: false },
{ id: 'marketing', name: 'Marketing', required: false }
],
translations: {
'pt-BR': {
title: 'Valorizamos sua privacidade',
description: 'Utilizamos cookies para melhorar sua experiência de navegação. Ao clicar em "Aceitar Todos", você concorda com o uso de cookies.',
acceptAll: 'Aceitar Todos',
rejectAll: 'Rejeitar Todos',
customize: 'Personalizar',
privacyPolicy: 'Política de Privacidade',
categories: {
necessary: {
name: 'Estritamente Necessários',
description: 'Esses cookies são essenciais para o funcionamento do site.'
},
analytics: {
name: 'Análise',
description: 'Nos ajudam a entender como os visitantes interagem com nosso site.'
},
marketing: {
name: 'Marketing',
description: 'Usados para exibir anúncios relevantes.'
}
}
}
}
},
consentModel: 'opt-in',
defaultState: {
necessary: true,
analytics: false,
marketing: false
}
}
];
// Geo-detection and config selection
class GeoAwareConsentManager {
private currentRegion: string = 'unknown';
private currentConfig: RegionPrivacyConfig | null = null;
async detectRegion(): Promise {
// Method 1: Use Shopify's built-in detection
if (window.Shopify?.customerPrivacy?.getRegion) {
return window.Shopify.customerPrivacy.getRegion();
}
// Method 2: Use Markets detected country
if (window.Shopify?.country) {
return this.mapCountryToRegion(window.Shopify.country);
}
// Method 3: Use IP-based detection
try {
const response = await fetch('/apps/privacy/detect-region');
const data = await response.json();
return data.region;
} catch {
return 'unknown';
}
}
private mapCountryToRegion(country: string): string {
const euCountries = ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE',
'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV',
'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK',
'SI', 'ES', 'SE'];
if (euCountries.includes(country)) return 'EU';
if (country === 'US') return 'US'; // Further detection needed for CA
if (country === 'CA') return 'CA';
if (country === 'BR') return 'BR';
if (country === 'GB') return 'GB';
return country;
}
async initialize(): Promise {
this.currentRegion = await this.detectRegion();
this.currentConfig = this.getConfigForRegion(this.currentRegion);
if (this.currentConfig) {
this.renderBanner();
}
}
private getConfigForRegion(region: string): RegionPrivacyConfig | null {
return regionConfigs.find(c => c.region === region) ||
regionConfigs.find(c => c.region === 'DEFAULT') ||
null;
}
private renderBanner(): void {
if (!this.currentConfig) return;
const config = this.currentConfig.bannerConfig;
const lang = this.detectLanguage();
const translations = config.translations[lang] || config.translations['en'];
// Create and inject banner HTML
const banner = this.createBannerElement(config, translations);
document.body.appendChild(banner);
// Set up event listeners
this.attachEventListeners(banner);
}
private detectLanguage(): string {
// Use Shopify locale or browser language
return window.Shopify?.locale ||
navigator.language.split('-')[0] ||
'en';
}
private createBannerElement(
config: BannerConfig,
translations: BannerTranslations
): HTMLElement {
const banner = document.createElement('div');
banner.id = 'privacy-consent-banner';
banner.className = `consent-banner consent-banner--${config.style} consent-banner--${config.position}`;
banner.innerHTML = `
We use cookies to enhance your shopping experience. Choose your preferences below.
{showDetails && (
)}
);
}
```
## Testing Your Shopify Consent Implementation
### Automated Testing Suite
```typescript
// shopify-consent-tests.spec.ts
import { test, expect, Page } from '@playwright/test';
test.describe('Shopify Consent Banner', () => {
let page: Page;
test.beforeEach(async ({ browser }) => {
// Clear cookies for fresh state
const context = await browser.newContext();
page = await context.newPage();
});
test('banner appears for EU visitors', async () => {
// Simulate EU visitor
await page.route('**/detect-region', route => {
route.fulfill({
status: 200,
body: JSON.stringify({ region: 'DE' })
});
});
await page.goto('https://your-store.myshopify.com');
// Banner should be visible
const banner = page.locator('#privacy-consent-banner');
await expect(banner).toBeVisible();
// Should have all required buttons
await expect(banner.locator('.consent-btn--accept')).toBeVisible();
await expect(banner.locator('.consent-btn--reject')).toBeVisible();
await expect(banner.locator('.consent-btn--customize')).toBeVisible();
});
test('scripts blocked before consent', async () => {
await page.goto('https://your-store.myshopify.com');
// Check that tracking scripts haven't fired
const gaLoaded = await page.evaluate(() => {
return typeof (window as any).ga !== 'undefined';
});
expect(gaLoaded).toBe(false);
const fbqLoaded = await page.evaluate(() => {
return typeof (window as any).fbq !== 'undefined';
});
expect(fbqLoaded).toBe(false);
});
test('accept all enables tracking', async () => {
await page.goto('https://your-store.myshopify.com');
// Accept all
await page.click('.consent-btn--accept');
// Wait for scripts to load
await page.waitForTimeout(2000);
// Check tracking enabled
const gaLoaded = await page.evaluate(() => {
return typeof (window as any).ga !== 'undefined' ||
typeof (window as any).gtag !== 'undefined';
});
expect(gaLoaded).toBe(true);
// Check consent state
const consentState = await page.evaluate(() => {
return (window as any).Shopify?.customerPrivacy?.analyticsProcessingAllowed();
});
expect(consentState).toBe(true);
});
test('reject all blocks tracking', async () => {
await page.goto('https://your-store.myshopify.com');
// Reject all
await page.click('.consent-btn--reject');
// Banner should hide
const banner = page.locator('#privacy-consent-banner');
await expect(banner).not.toBeVisible();
// Check consent state
const consentState = await page.evaluate(() => {
return (window as any).Shopify?.customerPrivacy?.marketingAllowed();
});
expect(consentState).toBe(false);
});
test('consent persists across page loads', async () => {
await page.goto('https://your-store.myshopify.com');
// Accept all
await page.click('.consent-btn--accept');
// Navigate to another page
await page.goto('https://your-store.myshopify.com/products');
// Banner should not appear
const banner = page.locator('#privacy-consent-banner');
await expect(banner).not.toBeVisible();
// Consent should persist
const consentState = await page.evaluate(() => {
return (window as any).Shopify?.customerPrivacy?.analyticsProcessingAllowed();
});
expect(consentState).toBe(true);
});
test('GPC signal is respected', async () => {
// Simulate GPC signal
await page.addInitScript(() => {
Object.defineProperty(navigator, 'globalPrivacyControl', {
value: true,
writable: false
});
});
await page.goto('https://your-store.myshopify.com');
// Sale of data should be blocked
const saleAllowed = await page.evaluate(() => {
return (window as any).Shopify?.customerPrivacy?.saleOfDataAllowed();
});
expect(saleAllowed).toBe(false);
});
test('checkout consent syncs correctly', async () => {
await page.goto('https://your-store.myshopify.com');
// Accept marketing
await page.click('.consent-btn--accept');
// Add product to cart
await page.goto('https://your-store.myshopify.com/products/test-product');
await page.click('button[name="add"]');
// Go to checkout
await page.goto('https://your-store.myshopify.com/checkout');
// Verify consent is available in checkout
const checkoutConsent = await page.evaluate(() => {
return (window as any).Shopify?.Checkout?.consent;
});
// Consent should be synced
expect(checkoutConsent).toBeTruthy();
});
});
test.describe('Web Pixel Consent', () => {
test('pixels respect consent state', async ({ page }) => {
await page.goto('https://your-store.myshopify.com');
// Reject consent
await page.click('.consent-btn--reject');
// Navigate to trigger page view
await page.goto('https://your-store.myshopify.com/collections/all');
// Check network requests - no tracking calls should be made
const requests: string[] = [];
page.on('request', request => {
requests.push(request.url());
});
await page.waitForTimeout(3000);
// Should not have GA or FB pixel requests
const hasTrackingRequests = requests.some(url =>
url.includes('google-analytics.com') ||
url.includes('facebook.com/tr')
);
expect(hasTrackingRequests).toBe(false);
});
});
```
## Make privacy your store advantage
Implementing proper cookie consent on Shopify is more than a compliance checkbox—it's an opportunity to build trust with your customers. Modern shoppers are increasingly privacy-conscious, and stores that respect their choices see higher conversion rates and customer loyalty. Clear consent flows also reduce the risk of App Store reviews being blocked or Meta/Google tags being throttled because they are not gated by consent.
### Key Implementation Checklist
| Task | Priority | Notes |
|------|----------|-------|
| Integrate Customer Privacy API | Critical | Foundation for all consent |
| Configure Web Pixel consent | Critical | Required for GA4, Meta |
| Implement geo-detection | High | Different rules per region |
| Add checkout consent (Plus) | High | Complete consent flow |
| Set up consent persistence | High | Remember user choices |
| Test with blocked cookies | Medium | Ensure site functions |
| Audit third-party apps | Medium | Know what data is shared |
| Implement GPC support | Medium | California requirement |
| Create preference center | Medium | Allow consent management |
| Document for compliance | Low | Maintain records |
### Tools and Resources
- **Shopify Customer Privacy API Documentation**: Official reference for the API
- **GetCookies Shopify App**: Full-featured CMP with Customer Privacy API integration
- **Shopify Web Pixels**: Guide to building compliant pixel extensions
- **GDPR for Shopify**: Shopify's official GDPR compliance guide
### Final Recommendations
1. **Start with Shopify's native tools** for simple single-region stores
2. **Upgrade to a third-party CMP** when selling to EU, California, or Brazil
3. **Always test your checkout flow** - this is where consent matters most
4. **Monitor app permissions** - third-party apps can introduce compliance gaps
5. **Keep consent records** - you may need them for regulatory audits
By following this guide, you'll have a Shopify store that respects customer privacy while maintaining the marketing capabilities needed for growth. Privacy and profitability aren't mutually exclusive—they're complementary when done right.
Vanlige spørsmål
Do I need a cookie banner on Shopify?
Yes, if you sell to customers in Europe (GDPR), UK, Switzerland, or US states like California, you are legally required to manage consent.
D
David Kim, WordPress Developer
Skribent hos GetCookies, spesialisert på personvernsamsvar, samtykkeadministrasjon og optimalisering av digital markedsføring.