The challenge

You want to test different cookie banner designs or messaging to improve consent rates while maintaining GDPR compliance.

The scenario

"I want to A/B test our cookie banner to see if different wording affects acceptance rates. But I'm worried about GDPR compliance if I track users before they consent."

Compliant A/B testing approach

The key principle: You can test cookie banners WITHOUT tracking tools, using only strictly necessary cookies.

Implementation

// Create variant assignment (strictly necessary cookie)
function assignVariant() {
    let variant = getCookie('banner_test_variant');

    if (!variant) {
        // Randomly assign variant
        variant = Math.random() < 0.5 ? 'A' : 'B';

        // Store in strictly necessary cookie (doesn't require consent)
        document.cookie = banner_test_variant=${variant}; path=/; max-age=2592000; SameSite=Strict;
    }

    return variant;
}

// Configuration for Variant A (Control)
const configA = {
    "notice_banner_type": "simple",
    "website_name": "My Website",
    // ... other config
};

// Configuration for Variant B (Test)
const configB = {
    "notice_banner_type": "headline",
    "website_name": "My Site", // Shorter name
    // ... other config
};

// Initialize based on variant
document.addEventListener('DOMContentLoaded', function () {
    const variant = assignVariant();
    const config = variant === 'A' ? configA : configB;

    // Add callback to track results (only after consent)
    config.callbacks = {
        "i_agree_button_clicked": () => {
            // User accepted - log this (now they've consented to tracking)
            logConsentEvent(variant, 'accepted');
        },
        "i_decline_button_clicked": () => {
            // User declined - still log the action
            logConsentEvent(variant, 'declined');
        }
    };

    cookieconsent.run(config);
});

function logConsentEvent(variant, action) {
    // Send to your analytics (user has now consented)
    if (typeof gtag !== 'undefined') {
        gtag('event', 'cookie_consent', {
            'variant': variant,
            'action': action
        });
    }

    // Or send to backend
    fetch('/api/log-consent', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({
            variant: variant,
            action: action,
            timestamp: new Date().toISOString()
        })
    });
}

function getCookie(name) {
    const value = ; ${document.cookie};
    const parts = value.split(; ${name}=);
    if (parts.length === 2) return parts.pop().split(';').shift();
}

Testing variables

Banner type

// Variant A: Simple banner
"notice_banner_type": "simple"

// Variant B: Headline banner
"notice_banner_type": "headline"

Color Scheme

// Variant A: Light
"palette": "light"

// Variant B: Dark
"palette": "dark"

Button text (via custom CSS)

// Add CSS to change button text
const style = document.createElement('style');
style.textContent = variant === 'A'
    ? '.cc-nb-okagree::before { content: "Accept All"; }'
    : '.cc-nb-okagree::before { content: "I Agree"; }';
document.head.appendChild(style);
// Backend endpoint to analyze results
// GET /api/consent-analytics

{
    "variant_A": {
        "impressions": 1000,
        "accepted": 650,
        "declined": 250,
        "no_action": 100,
        "acceptance_rate": 0.65
    },
    "variant_B": {
        "impressions": 1000,
        "accepted": 720,
        "declined": 200,
        "no_action": 80,
        "acceptance_rate": 0.72
    }
}

Problem

"We have www.example.com for our main site and shop.example.com for our store. Users get the cookie banner on BOTH sites even after accepting on the first one. It's annoying and we're losing conversions."

Understanding cross-domain challenges

By default, cookies are scoped to the specific domain where they're set:

  • Cookie set on www.example.com → Not accessible on shop.example.com
  • Cookie set on shop.example.com → Not accessible on www.example.com

Use the cookie_domain parameter to share consent across subdomains:

cookieconsent.run({
    "consent_type": "express",
    "website_name": "My Website",
    "website_privacy_policy_url": "https://www.example.com/privacy",
    "cookie_domain": ".example.com", // Note the leading dot!
    "page_load_consent_levels": ["strictly-necessary"]
});

Important! The leading dot (.example.com) makes the cookie accessible to:

  • example.com
  • www.example.com
  • shop.example.com
  • blog.example.com
  • Any subdomain of example.com

A complete cross-domain example

On all domains/subdomains, use identical configuration:

<!-- On www.example.com -->
<script type="text/javascript" src="//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
    cookieconsent.run({
        "notice_banner_type": "headline",
        "consent_type": "express",
        "palette": "light",
        "language": "en",
        "website_name": "Example",
        "website_privacy_policy_url": "https://www.example.com/privacy",
        "open_preferences_center_selector": "#changePreferences",
        "page_load_consent_levels": ["strictly-necessary"],
        "cookie_domain": ".example.com", // Shared across all subdomains
        "cookie_secure": true // Recommended for production
    });
});
</script>
<!-- On shop.example.com - SAME configuration -->
<script type="text/javascript" src="//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
    cookieconsent.run({
        // EXACT SAME CONFIG as main domain
        "notice_banner_type": "headline",
        "consent_type": "express",
        "palette": "light",
        "language": "en",
        "website_name": "Example",
        "website_privacy_policy_url": "https://www.example.com/privacy",
        "open_preferences_center_selector": "#changePreferences",
        "page_load_consent_levels": ["strictly-necessary"],
        "cookie_domain": ".example.com", // Same domain!
        "cookie_secure": true
    });
});
</script>

Test cross-domain setup

  1. Visit www.example.com
  2. Accept cookies
  3. Open DevTools → Application → Cookies
  4. Verify that cookie domain shows .example.com (with leading dot)
  5. Navigate to shop.example.com
  6. Verify banner does NOT appear (consent already given)

Common cross-domain issues

Issue 1: Banner still appears on subdomains

Problem

"I set cookie_domain to '.example.com' but the banner still shows on every subdomain."

Cause

The browser is blocking third-party cookies.

Solution

Make sure cookie_secure is true for HTTPS sites.

// Make sure cookie_secure is true for HTTPS sites
"cookie_secure": true,

// Also verify in DevTools that cookie is actually being set
// Check: Application → Cookies → Look for cookie_consent_level
// Domain should show: .example.com (not www.example.com)

Problem

The user provides consent on main domain, but preferences differ on subdomain.

Cause

Configurations are not identical across domains.

Solution

Use a shared configuration file or variable.

// shared-config.js (include on all domains)
const SHARED_COOKIE_CONFIG = {
    "notice_banner_type": "headline",
    "consent_type": "express",
    "palette": "light",
    "language": "en",
    "website_name": "Example",
    "website_privacy_policy_url": "https://www.example.com/privacy",
    "open_preferences_center_selector": "#changePreferences",
    "page_load_consent_levels": ["strictly-necessary"],
    "cookie_domain": ".example.com",
    "cookie_secure": true
};

// Then on each domain:
cookieconsent.run(SHARED_COOKIE_CONFIG);

Multi-domain (different TLDs)

Problem

You have example.com AND example.co.uk, so completely different domains.

Solution

Cookies CANNOT be shared between different top-level domains due to browser security.

You have two options:

Option 1: Accept multiple banners.

  • User sees banner on each top-level domain
  • Consent given applies individually for each domain, consent is not shared
  • This is likely the most compliant approach

Option 2: Use a backend sync (for logged-in users)

// On example.com - user accepts
"callbacks": {
    "user_consent_saved": () => {
        if (isUserLoggedIn()) {
            // Sync to backend
            fetch('https://api.example.com/sync-consent', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': 'Bearer ' + getUserToken()
                },
                body: JSON.stringify({
                    consent: getConsentState(),
                    domains: ['example.com', 'example.co.uk']
                })
            });
        }
    }
}

// On example.co.uk - check backend first
async function checkBackendConsent() {
    if (isUserLoggedIn()) {
        const response = await fetch('https://api.example.com/get-consent', {
            headers: {'Authorization': 'Bearer ' + getUserToken()}
        });
        const data = await response.json();
        if (data.hasConsent) {
            // Set cookie locally
            setCookieConsent(data.consentLevel);
        }
    }
}

Problem

"After user accepts cookies, I reload the page with window.location.reload() to make sure tracking scripts load. But this causes Google Analytics to lose the original referrer source and shows all traffic as 'Direct'."

This happens because when you reload the page:

  1. The browser clears referrer information
  2. The new page load appears to come from same domain
  3. Google Analytics categorizes as "Direct" traffic
  4. You lose attribution data

Solution 1: Don't reload (preferred)

Use callbacks to dynamically load scripts instead of reloading:

cookieconsent.run({
    "notice_banner_type": "headline",
    "consent_type": "express",
    "palette": "light",
    "language": "en",
    "website_name": "My Site",
    "website_privacy_policy_url": "https://mysite.com/privacy",
    "page_load_consent_levels": ["strictly-necessary"],

    "callbacks": {
        "scripts_specific_loaded": (level) => {
            // Scripts are NOW loaded - no reload needed!
            console.log("Scripts loaded for: " + level);

            if (level === 'tracking') {
                // Analytics is now active
                // Send initial page view if needed
                if (typeof gtag !== 'undefined') {
                    gtag('event', 'page_view', {
                        'page_location': window.location.href,
                        'page_referrer': document.referrer
                    });
                }
            }
        }
    },
    "callbacks_force": true
});

Solution 2: Preserve referrer on page reload

If you MUST reload, preserve the referrer:

cookieconsent.run({
    // ... config ...
    "callbacks": {
        "i_agree_button_clicked": () => {
            // Store referrer and source before reload
            sessionStorage.setItem('original_referrer', document.referrer);
            sessionStorage.setItem('landing_page', window.location.href);

            // Get URL parameters (utm_source, etc.)
            const urlParams = new URLSearchParams(window.location.search);
            if (urlParams.has('utm_source')) {
                sessionStorage.setItem('utm_source', urlParams.get('utm_source'));
                sessionStorage.setItem('utm_medium', urlParams.get('utm_medium'));
                sessionStorage.setItem('utm_campaign', urlParams.get('utm_campaign'));
            }

            // Now reload
            window.location.reload();
        }
    }
});

// After reload, restore attribution
document.addEventListener('DOMContentLoaded', function() {
    const savedReferrer = sessionStorage.getItem('original_referrer');
    const utmSource = sessionStorage.getItem('utm_source');

    if (savedReferrer && typeof gtag !== 'undefined') {
        // Send page view with preserved referrer
        gtag('event', 'page_view', {
            'page_location': window.location.href,
            'page_referrer': savedReferrer
        });

        // Send campaign data if available
        if (utmSource) {
            gtag('event', 'campaign_details', {
                'campaign_source': utmSource,
                'campaign_medium': sessionStorage.getItem('utm_medium'),
                'campaign_name': sessionStorage.getItem('utm_campaign')
            });
        }

        // Clear storage
        sessionStorage.removeItem('original_referrer');
        sessionStorage.removeItem('utm_source');
        // ... clear others
    }
});

With Consent Mode V2, a reload is NOT needed:

<!-- 1. Set default consent to denied -->
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('consent', 'default', {
    'ad_storage': 'denied',
    'ad_user_data': 'denied',
    'ad_personalization': 'denied',
    'analytics_storage': 'denied'
});
</script>

<!-- 2. Load Google Analytics (runs in consent mode) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>

<!-- 3. Cookie Consent with callbacks -->
<script src="//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
    cookieconsent.run({
        "consent_type": "express",
        "website_name": "My Site",
        "page_load_consent_levels": ["strictly-necessary"],
        "callbacks": {
            "scripts_specific_loaded": (level) => {
                // Update consent mode - NO RELOAD!
                switch (level) {
                    case 'tracking':
                        gtag('consent', 'update', {
                            'analytics_storage': 'granted'
                        });
                        break;
                    case 'targeting':
                        gtag('consent', 'update', {
                            'ad_storage': 'granted',
                            'ad_user_data': 'granted',
                            'ad_personalization': 'granted'
                        });
                        break;
                }
            }
        },
        "callbacks_force": true
    });
});
  </script>


Embedded Content and iFrames

Problem

"We have YouTube videos embedded on our site. Should we block the iframes until users accept cookies? How do we implement this with TermsFeed?"

Solution

The solution is to integrate a placeholder and use dynamic loading.

Step 1: Create Placeholder HTML

<!-- Instead of directly embedding iframe -->
<div class="video-placeholder" data-video-id="dQw4w9WgXcQ" data-consent-type="targeting">
    <div class="placeholder-content">
        <img src="/images/video-thumbnail.jpg" alt="Video thumbnail">
        <div class="placeholder-overlay">
            <button class="accept-and-load" onclick="loadVideo(this)">
                 Accept cookies to watch this video
            </button>
            <p>This video is hosted by YouTube and requires marketing cookies.</p>
            <a href="#" onclick="openCookiePreferences(); return false;">
                Change cookie preferences
            </a>
        </div>
    </div>
</div>

<style>
.video-placeholder {
    position: relative;
    width: 100%;
    padding-bottom: 56.25%; // 16:9 aspect ratio
    background: #000;
}

.placeholder-content {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

.placeholder-content img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.placeholder-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.7);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    color: white;
    text-align: center;
    padding: 20px;
}

.accept-and-load {
    background: #ff0000;
    color: white;
    border: none;
    padding: 15px 30px;
    font-size: 16px;
    border-radius: 5px;
    cursor: pointer;
    margin-bottom: 15px;
}
</style>
// Check if user has already consented
function hasConsent(consentType) {
    const consentCookie = getCookie('cookie_consent_level');
    if (!consentCookie) return false;

    // Parse consent levels
    const levels = JSON.parse(decodeURIComponent(consentCookie));
    return levels.includes(consentType);
}

// Load video dynamically
function loadVideo(button) {
    const placeholder = button.closest('.video-placeholder');
    const videoId = placeholder.dataset.videoId;
    const consentType = placeholder.dataset.consentType;

    // Check if user has consent
    if (!hasConsent(consentType)) {
        // Request consent
        if (typeof cookieconsent !== 'undefined' && cookieconsent.openPreferencesCenter) {
            cookieconsent.openPreferencesCenter();
        } else {
            alert('Please accept marketing cookies to view this content');
        }
        return;
    }

    // Create iframe
    const iframe = document.createElement('iframe');
    iframe.src = https://www.youtube.com/embed/${videoId};
    iframe.width = '100%';
    iframe.height = '100%';
    iframe.frameBorder = '0';
    iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
    iframe.allowFullscreen = true;

    // Replace placeholder with iframe
    placeholder.innerHTML = '';
    placeholder.appendChild(iframe);
}

// Auto-load videos if consent already given
document.addEventListener('DOMContentLoaded', function() {
    document.querySelectorAll('.video-placeholder').forEach(placeholder => {
        const consentType = placeholder.dataset.consentType;
        if (hasConsent(consentType)) {
            // Auto-load - user already consented
            const videoId = placeholder.dataset.videoId;
            const iframe = document.createElement('iframe');
            iframe.src = https://www.youtube.com/embed/${videoId};
            iframe.width = '100%';
            iframe.height = '100%';
            iframe.frameBorder = '0';
            iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
            iframe.allowFullscreen = true;
            placeholder.innerHTML = '';
            placeholder.appendChild(iframe);
        }
    });
});

// Listen for consent updates
document.addEventListener('DOMContentLoaded', function() {
    cookieconsent.run({
        // ... config ...
        "callbacks": {
            "scripts_specific_loaded": (level) => {
                // When user accepts targeting cookies, load all pending videos
                if (level === 'targeting') {
                    document.querySelectorAll('.video-placeholder').forEach(placeholder => {
                        if (placeholder.dataset.consentType === 'targeting') {
                            loadVideo(placeholder.querySelector('.accept-and-load'));
                        }
                    });
                }
            }
        }
    });
});

function getCookie(name) {
    const value = ; ${document.cookie};
    const parts = value.split(; ${name}=);
    if (parts.length === 2) return parts.pop().split(';').shift();
}

function openCookiePreferences() {
    if (typeof cookieconsent !== 'undefined' && cookieconsent.openPreferencesCenter) {
        cookieconsent.openPreferencesCenter();
    }
}

Example for other embeds

Google Maps

<div class="map-placeholder" data-consent-type="functionality" data-location="New York, NY">
    <div class="placeholder-overlay">
        <p>️ Accept functional cookies to view map</p>
        <button onclick="loadMap(this)">Load Map</button>
    </div>
</div>

<script>
function loadMap(button) {
    const placeholder = button.closest('.map-placeholder');
    const location = placeholder.dataset.location;

    if (!hasConsent('functionality')) {
        cookieconsent.openPreferencesCenter();
        return;
    }

    const iframe = document.createElement('iframe');
    iframe.src = https://www.google.com/maps/embed/v1/place?key=YOUR_API_KEY&q=${encodeURIComponent(location)};
    iframe.width = '100%';
    iframe.height = '450';
    iframe.style.border = '0';
    iframe.allowFullscreen = true;
    iframe.loading = 'lazy';

    placeholder.innerHTML = '';
    placeholder.appendChild(iframe);
}
</script>

Social media embeds (X, Instagram, Facebook)

<div class="social-placeholder" data-consent-type="targeting" data-platform="twitter" data-url="https://twitter.com/user/status/123">
    <div class="placeholder-overlay">
        <p> Accept marketing cookies to view this tweet</p>
        <button onclick="loadSocialEmbed(this)">Load Tweet</button>
    </div>
</div>

<script>
function loadSocialEmbed(button) {
    const placeholder = button.closest('.social-placeholder');
    const platform = placeholder.dataset.platform;
    const url = placeholder.dataset.url;

    if (!hasConsent('targeting')) {
        cookieconsent.openPreferencesCenter();
        return;
    }

    // Load platform SDK
    if (platform === 'twitter' && !window.twttr) {
        const script = document.createElement('script');
        script.src = 'https://platform.twitter.com/widgets.js';
        script.onload = () => {
            twttr.widgets.createTweet(
                url.split('/').pop(),
                placeholder,
                { theme: 'light' }
            );
        };
        document.head.appendChild(script);
    }
    // Add other platforms as needed
}
</script>