Tillbaka till bloggen
Technical

Shopify Privacy API: Implementing Consent in E-commerce

GetCookies-teametNovember 15, 202513 min läsning
ShopifyE-commerceAPIIntegration

TLDR: Shopify's ecosystem requires careful consent integration—many apps ignore consent signals entirely.

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:

// 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<PrivacyAuditReport> {
    const report: PrivacyAuditReport = {
      totalApps: this.apps.length,
      highRiskApps: [],
      dataCategories: new Set<string>(),
      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<string>;
  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

// 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

// 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<string, Function[]> = new Map();

  constructor() {
    this.consentState = {
      analytics: false,
      marketing: false,
      preferences: false,
      saleOfData: false
    };
  }

  async initialize(): Promise<void> {
    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<ShopifyConsentState>,
    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<void> {
    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

// 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<void> {
    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

// 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<string, PixelConfiguration> = {
  '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<string, PixelConfiguration> = new Map();
  private userRegion: string = 'unknown';
  private consentState: Record<string, boolean> = {};

  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<string, string[]> = {
      '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

// 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',
  () => <CheckoutConsentExtension />
);

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 (
    <BlockStack spacing="base">
      <Text size="medium" emphasis="bold">
        Communication Preferences
      </Text>

      <Checkbox
        checked={emailConsent}
        onChange={(checked) => handleConsentChange('email', checked)}
      >
        <Text>
          I agree to receive marketing emails about products, offers, and updates
        </Text>
      </Checkbox>

      <Checkbox
        checked={smsConsent}
        onChange={(checked) => handleConsentChange('sms', checked)}
      >
        <Text>
          I agree to receive SMS marketing messages (msg & data rates may apply)
        </Text>
      </Checkbox>

      <Checkbox
        checked={thirdPartyConsent}
        onChange={(checked) => handleConsentChange('third_party', checked)}
      >
        <Text>
          I allow sharing my data with trusted partners for personalized offers
        </Text>
      </Checkbox>

      <Text size="small" appearance="subdued">
        You can unsubscribe at any time. See our{' '}
        <Link to="/policies/privacy-policy">Privacy Policy</Link> for details.
      </Text>

      {requiresExplicitConsent() && (
        <Banner status="info">
          Based on your location, explicit consent is required for marketing communications.
        </Banner>
      )}
    </BlockStack>
  );
}

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

// 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<void> {
  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<ConsentRecord> {
  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<void> {
  // 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<void> {
  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

// geo-aware-consent-banner.ts
interface RegionPrivacyConfig {
  region: string;
  displayName: string;
  regulations: string[];
  bannerConfig: BannerConfig;
  consentModel: 'opt-in' | 'opt-out' | 'notice-only';
  defaultState: Record<string, boolean>;
}

interface BannerConfig {
  showOnLoad: boolean;
  blockScripts: boolean;
  position: 'bottom' | 'top' | 'center';
  style: 'bar' | 'modal' | 'popup';
  categories: ConsentCategory[];
  translations: Record<string, BannerTranslations>;
}

interface BannerTranslations {
  title: string;
  description: string;
  acceptAll: string;
  rejectAll: string;
  customize: string;
  privacyPolicy: string;
  categories: Record<string, CategoryTranslation>;
}

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<string> {
    // 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<void> {
    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 = `
      <div class="consent-banner__content">
        <h2 class="consent-banner__title">${translations.title}</h2>
        <p class="consent-banner__description">${translations.description}</p>

        <div class="consent-banner__categories" style="display: none;">
          ${config.categories.map(cat => `
            <label class="consent-category">
              <input
                type="checkbox"
                name="consent-${cat.id}"
                ${cat.required ? 'checked disabled' : ''}
                ${this.currentConfig?.defaultState[cat.id] ? 'checked' : ''}
              />
              <span class="consent-category__name">
                ${translations.categories[cat.id]?.name || cat.name}
              </span>
              <span class="consent-category__description">
                ${translations.categories[cat.id]?.description || ''}
              </span>
            </label>
          `).join('')}
        </div>

        <div class="consent-banner__actions">
          <button class="consent-btn consent-btn--reject">
            ${translations.rejectAll}
          </button>
          <button class="consent-btn consent-btn--customize">
            ${translations.customize}
          </button>
          <button class="consent-btn consent-btn--accept">
            ${translations.acceptAll}
          </button>
        </div>

        <a href="/policies/privacy-policy" class="consent-banner__policy-link">
          ${translations.privacyPolicy}
        </a>
      </div>
    `;

    return banner;
  }

  private attachEventListeners(banner: HTMLElement): void {
    banner.querySelector('.consent-btn--accept')?.addEventListener('click', () => {
      this.acceptAll();
      this.hideBanner(banner);
    });

    banner.querySelector('.consent-btn--reject')?.addEventListener('click', () => {
      this.rejectAll();
      this.hideBanner(banner);
    });

    banner.querySelector('.consent-btn--customize')?.addEventListener('click', () => {
      const categories = banner.querySelector('.consent-banner__categories');
      if (categories) {
        categories.style.display = categories.style.display === 'none' ? 'block' : 'none';
      }
    });
  }

  private acceptAll(): void {
    const consent: Record<string, boolean> = {};
    this.currentConfig?.bannerConfig.categories.forEach(cat => {
      consent[cat.id] = true;
    });
    this.setConsent(consent);
  }

  private rejectAll(): void {
    const consent: Record<string, boolean> = {};
    this.currentConfig?.bannerConfig.categories.forEach(cat => {
      consent[cat.id] = cat.required;
    });
    this.setConsent(consent);
  }

  private setConsent(consent: Record<string, boolean>): void {
    // Set Shopify consent
    const marketingConsent = consent.marketing || consent.sale_sharing || false;

    window.Shopify?.customerPrivacy?.setTrackingConsent(marketingConsent, () => {
      console.log('Consent set via Customer Privacy API');
    });

    // Store granular consent
    localStorage.setItem('privacy_consent', JSON.stringify({
      consent,
      timestamp: new Date().toISOString(),
      region: this.currentRegion
    }));

    // Trigger consent update event
    window.dispatchEvent(new CustomEvent('consent:updated', {
      detail: consent
    }));
  }

  private hideBanner(banner: HTMLElement): void {
    banner.style.display = 'none';
  }
}

// Initialize
const geoConsentManager = new GeoAwareConsentManager();
document.addEventListener('DOMContentLoaded', () => {
  geoConsentManager.initialize();
});

Headless Shopify and Consent

For headless Shopify implementations using Hydrogen or custom frontends, consent management requires special handling.

Hydrogen Consent Component

// hydrogen-consent-provider.tsx
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { useShop, useCart } from '@shopify/hydrogen';

interface ConsentState {
  necessary: boolean;
  analytics: boolean;
  marketing: boolean;
  preferences: boolean;
  initialized: boolean;
}

interface ConsentContextValue {
  consent: ConsentState;
  updateConsent: (updates: Partial<ConsentState>) => void;
  acceptAll: () => void;
  rejectAll: () => void;
  shouldShowBanner: boolean;
}

const ConsentContext = createContext<ConsentContextValue | null>(null);

export function ConsentProvider({ children }: { children: ReactNode }) {
  const { countryIsoCode } = useShop();
  const cart = useCart();

  const [consent, setConsent] = useState<ConsentState>({
    necessary: true,
    analytics: false,
    marketing: false,
    preferences: false,
    initialized: false
  });

  const [shouldShowBanner, setShouldShowBanner] = useState(false);

  useEffect(() => {
    // Load stored consent
    const stored = localStorage.getItem('hydrogen_consent');
    if (stored) {
      try {
        const parsed = JSON.parse(stored);
        setConsent({ ...parsed, initialized: true });
      } catch {
        initializeDefaultConsent();
      }
    } else {
      initializeDefaultConsent();
    }
  }, []);

  useEffect(() => {
    // Sync consent with cart attributes for checkout
    if (consent.initialized && cart?.id) {
      syncConsentToCart();
    }
  }, [consent, cart?.id]);

  const initializeDefaultConsent = () => {
    const requiresOptIn = isOptInRegion(countryIsoCode);

    setConsent({
      necessary: true,
      analytics: !requiresOptIn,
      marketing: !requiresOptIn,
      preferences: !requiresOptIn,
      initialized: true
    });

    setShouldShowBanner(true);
  };

  const isOptInRegion = (country: string): boolean => {
    const optInCountries = [
      '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', 'GB', 'NO', 'IS',
      'LI', 'CH', 'BR'
    ];
    return optInCountries.includes(country);
  };

  const updateConsent = (updates: Partial<ConsentState>) => {
    const newConsent = { ...consent, ...updates };
    setConsent(newConsent);

    // Persist
    localStorage.setItem('hydrogen_consent', JSON.stringify(newConsent));

    // Update tracking scripts
    updateTrackingScripts(newConsent);

    setShouldShowBanner(false);
  };

  const acceptAll = () => {
    updateConsent({
      necessary: true,
      analytics: true,
      marketing: true,
      preferences: true
    });
  };

  const rejectAll = () => {
    updateConsent({
      necessary: true,
      analytics: false,
      marketing: false,
      preferences: false
    });
  };

  const syncConsentToCart = async () => {
    // Sync consent state to cart attributes for checkout awareness
    try {
      await cart.cartAttributesUpdate([
        { key: 'consent_analytics', value: String(consent.analytics) },
        { key: 'consent_marketing', value: String(consent.marketing) },
        { key: 'consent_preferences', value: String(consent.preferences) }
      ]);
    } catch (error) {
      console.error('Failed to sync consent to cart:', error);
    }
  };

  const updateTrackingScripts = (newConsent: ConsentState) => {
    // Google Consent Mode
    if (typeof gtag === 'function') {
      gtag('consent', 'update', {
        analytics_storage: newConsent.analytics ? 'granted' : 'denied',
        ad_storage: newConsent.marketing ? 'granted' : 'denied',
        ad_user_data: newConsent.marketing ? 'granted' : 'denied',
        ad_personalization: newConsent.marketing ? 'granted' : 'denied'
      });
    }

    // Meta Pixel
    if (typeof fbq === 'function') {
      if (newConsent.marketing) {
        fbq('consent', 'grant');
      } else {
        fbq('consent', 'revoke');
      }
    }

    // Custom event for other integrations
    window.dispatchEvent(new CustomEvent('hydrogen:consent', {
      detail: newConsent
    }));
  };

  return (
    <ConsentContext.Provider value={{
      consent,
      updateConsent,
      acceptAll,
      rejectAll,
      shouldShowBanner
    }}>
      {children}
    </ConsentContext.Provider>
  );
}

export function useConsent() {
  const context = useContext(ConsentContext);
  if (!context) {
    throw new Error('useConsent must be used within ConsentProvider');
  }
  return context;
}

// Consent Banner Component
export function ConsentBanner() {
  const { consent, acceptAll, rejectAll, shouldShowBanner, updateConsent } = useConsent();
  const [showDetails, setShowDetails] = useState(false);

  if (!shouldShowBanner) return null;

  return (
    <div className="fixed bottom-0 left-0 right-0 bg-white shadow-lg p-6 z-50">
      <div className="max-w-4xl mx-auto">
        <h2 className="text-lg font-semibold mb-2">Privacy Settings</h2>
        <p className="text-gray-600 mb-4">
          We use cookies to enhance your shopping experience. Choose your preferences below.
        </p>

        {showDetails && (
          <div className="space-y-3 mb-4">
            <label className="flex items-center">
              <input
                type="checkbox"
                checked={consent.analytics}
                onChange={(e) => updateConsent({ analytics: e.target.checked })}
                className="mr-2"
              />
              <span>Analytics - Help us improve our store</span>
            </label>

            <label className="flex items-center">
              <input
                type="checkbox"
                checked={consent.marketing}
                onChange={(e) => updateConsent({ marketing: e.target.checked })}
                className="mr-2"
              />
              <span>Marketing - Personalized ads and recommendations</span>
            </label>

            <label className="flex items-center">
              <input
                type="checkbox"
                checked={consent.preferences}
                onChange={(e) => updateConsent({ preferences: e.target.checked })}
                className="mr-2"
              />
              <span>Preferences - Remember your settings</span>
            </label>
          </div>
        )}

        <div className="flex gap-3">
          <button
            onClick={rejectAll}
            className="px-4 py-2 border border-gray-300 rounded hover:bg-gray-50"
          >
            Reject All
          </button>

          <button
            onClick={() => setShowDetails(!showDetails)}
            className="px-4 py-2 border border-gray-300 rounded hover:bg-gray-50"
          >
            {showDetails ? 'Hide Details' : 'Customize'}
          </button>

          <button
            onClick={acceptAll}
            className="px-4 py-2 bg-black text-white rounded hover:bg-gray-800"
          >
            Accept All
          </button>
        </div>
      </div>
    </div>
  );
}

Testing Your Shopify Consent Implementation

Automated Testing Suite

// 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.

Vanliga frågor

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.
G

GetCookies-teamet

Skriven och underhållen av GetCookies-teamet på Getia AS i Oslo, Norge. Artikeln är allmän information om cookiesamtycke, inte juridisk rådgivning.

Skanna din egen webbplats gratis

Gratisplanen kör bannern på en webbplats med tre cookieskanningar i månaden. Inget kort, inget säljsamtal.