CookieFrameDocs

Manual Installation

Complete guide to installing CookieFrame directly on your website via HTML script tag, with advanced configuration options and JavaScript API reference

Manual Installation

This guide covers how to install CookieFrame directly on your website by adding a script tag to your HTML. This method gives you the most control over the integration and works with any website regardless of the CMS or framework you use.

When to Use Manual Installation

Manual installation is ideal when:

  • You don't use Google Tag Manager
  • Your CMS isn't supported with a dedicated plugin
  • You need full control over script loading
  • You want to programmatically interact with consent via JavaScript
  • You're building a custom integration

If you use Google Tag Manager, we recommend the GTM integration instead, as it includes built-in consent mode support with less configuration.

Prerequisites

Before you begin, make sure you have:

Finding Your Domain ID

  1. Log in to your CookieFrame Dashboard
  2. Select your domain from the list
  3. Go to SettingsIntegration
  4. Your Domain ID is shown at the top (8 uppercase characters, e.g., A1B2C3D4)

Basic Installation

Add this script tag to your website's <head> section:

<script src="https://cookieframe.com/api/widget/YOUR_DOMAIN_ID/script.js" async></script>

Replace YOUR_DOMAIN_ID with your 8-character Domain ID.

Example

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Your Website</title>

  <!-- CookieFrame - MUST be before any other scripts -->
  <script src="https://cookieframe.com/api/widget/A1B2C3D4/script.js" async></script>

  <!-- Other scripts (analytics, marketing, etc.) -->
  <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX"></script>
</head>
<body>
  <!-- Your website content -->
</body>
</html>

Important: Place the CookieFrame script before any analytics, marketing, or tracking scripts. This ensures consent is established before those scripts try to set cookies.


Script Loading Behavior

Asynchronous Loading

The async attribute loads the script without blocking page rendering. This is recommended for performance:

<script src="https://cookieframe.com/api/widget/YOUR_DOMAIN_ID/script.js" async></script>

Synchronous Loading

If you need the widget to be fully loaded before the page renders (rare cases), remove the async attribute:

<script src="https://cookieframe.com/api/widget/YOUR_DOMAIN_ID/script.js"></script>

The script is lightweight (~15KB gzipped) and loads from Vercel's edge network, so asynchronous loading is recommended for most use cases.

Deferred Loading

For maximum performance, you can use defer instead of async. This loads the script after HTML parsing but before DOMContentLoaded:

<script src="https://cookieframe.com/api/widget/YOUR_DOMAIN_ID/script.js" defer></script>

How the Widget Works

When the script loads, CookieFrame:

  1. Fetches configuration from the pre-baked script (no additional API call needed)
  2. Detects visitor location to determine the applicable privacy framework (GDPR, CCPA, or Notice)
  3. Checks for existing consent in localStorage and on the server
  4. Shows the consent banner if no valid consent exists
  5. Initializes script blocking if enabled in your settings
  6. Exposes the JavaScript API via window.CookieFrame

Pre-Baked Configuration

Unlike some consent tools, CookieFrame pre-bakes your configuration into the script URL. This means:

  • No additional API call to fetch configuration
  • Faster initial load time
  • Configuration is cached at the CDN edge

Configuration caches for 2 minutes at the CDN with stale-while-revalidate for 5 minutes, so changes in your dashboard appear quickly.


GDPR and other privacy regulations require you to let users change their cookie preferences at any time. Add a "Cookie Preferences" or "Manage Cookies" link to your footer or privacy page.

The simplest approach—add the data-cookieframe="preferences" attribute to any element:

<a href="#" data-cookieframe="preferences">Cookie Preferences</a>

CookieFrame automatically binds a click handler when the widget loads. This also works with buttons or any other element:

<button data-cookieframe="preferences">Manage Cookie Settings</button>

For non-interactive elements like <span> or <div>, CookieFrame adds role="button", tabindex="0", and keyboard support automatically.

Option 2: JavaScript API

For more control, call the API directly:

<a href="#" onclick="window.CookieFrame && window.CookieFrame.showPreferences(); return false;">
  Cookie Preferences
</a>

Option 3: Queue-Based (Handles Race Conditions)

If the link might be clicked before CookieFrame fully loads:

<a href="#" id="cookie-prefs-link">Cookie Preferences</a>
<script>
  document.getElementById('cookie-prefs-link').addEventListener('click', function(e) {
    e.preventDefault();
    if (window.CookieFrame) {
      window.CookieFrame.showPreferences();
    } else {
      // Queue the action for when CookieFrame is ready
      window.CookieFrameQueue = window.CookieFrameQueue || [];
      window.CookieFrameQueue.push(function(widget) {
        widget.showPreferences();
      });
    }
  });
</script>

The preferences link only works on pages where the CookieFrame script is loaded. If you have a dedicated privacy page without the script, add the script there too.


JavaScript API Reference

CookieFrame exposes a comprehensive JavaScript API via window.CookieFrame. Use this for custom integrations, conditional script loading, or advanced consent management.

Waiting for the Widget

The widget loads asynchronously, so you should wait for it to be ready:

// Method 1: Queue-based (recommended)
window.CookieFrameQueue = window.CookieFrameQueue || [];
window.CookieFrameQueue.push(function(widget) {
  console.log('CookieFrame is ready!');
  console.log('Current consent:', widget.getConsent());
});

// Method 2: Event-based
if (window.CookieFrame) {
  // Already loaded
  console.log('Current consent:', window.CookieFrame.getConsent());
} else {
  // Wait for it
  window.addEventListener('load', function() {
    if (window.CookieFrame) {
      console.log('Current consent:', window.CookieFrame.getConsent());
    }
  });
}

Core Methods

Show/Hide Banner

// Show the consent banner
window.CookieFrame.show();

// Hide the consent banner
window.CookieFrame.hide();

// Show the preferences panel
window.CookieFrame.showPreferences();
// Get current consent preferences (or null if not consented)
const consent = window.CookieFrame.getConsent();
// Returns: { necessary: true, preferences: true, analytics: false, marketing: false }

// Check if user has given consent
const hasConsent = window.CookieFrame.hasConsent();
// Returns: true or false
// Accept all cookies
await window.CookieFrame.acceptAll();

// Reject all optional cookies (keeps necessary)
await window.CookieFrame.rejectAll();

// Update with specific preferences
await window.CookieFrame.updateConsent({
  necessary: true,    // Always true, cannot be disabled
  preferences: true,
  analytics: true,
  marketing: false
});

// Withdraw consent (clears all consent, shows banner again)
await window.CookieFrame.withdrawConsent();

// CCPA: Opt out of data sale (disables marketing only)
await window.CookieFrame.doNotSell();

Configuration and Metadata

// Get the full widget configuration
const config = window.CookieFrame.getConfig();

// Get the visitor's unique ID (UUID stored in localStorage)
const visitorId = window.CookieFrame.getVisitorId();

// Get the detected compliance framework
const framework = window.CookieFrame.getFramework();
// Returns: "gdpr", "ccpa", or "notice"

Cleanup

// Destroy the widget and clean up all resources
window.CookieFrame.destroy();

CookieFrame uses four standard categories:

CategoryKeyDescription
NecessarynecessaryEssential for website functionality. Always enabled, cannot be disabled.
PreferencespreferencesRemember user preferences like language or theme.
AnalyticsanalyticsTrack website usage for performance insights.
MarketingmarketingEnable advertising and remarketing features.

Custom categories configured in your dashboard are also available using their slug as the key.


Events

Listen for consent changes and widget events to trigger custom logic:

window.CookieFrameQueue = window.CookieFrameQueue || [];
window.CookieFrameQueue.push(function(widget) {

  // Consent given (initial or updated)
  widget.on('consent:given', function(consent, action) {
    console.log('Consent given:', consent);
    console.log('Action:', action); // 'accept_all', 'reject_all', 'custom', 'update'

    if (consent.analytics) {
      // Initialize analytics
      initializeGoogleAnalytics();
    }
  });

  // Consent updated (any change)
  widget.on('consent:updated', function(consent) {
    console.log('Consent updated:', consent);
  });

  // Consent withdrawn
  widget.on('consent:withdrawn', function() {
    console.log('User withdrew consent');
    // Clean up tracking scripts, cookies, etc.
  });

  // CCPA opt-out
  widget.on('ccpa:opt-out', function(consent) {
    console.log('User opted out of data sale');
  });

  // Widget visibility
  widget.on('widget:show', function() {
    console.log('Banner is now visible');
  });

  widget.on('widget:hide', function() {
    console.log('Banner hidden');
  });

  widget.on('preferences:show', function() {
    console.log('Preferences panel opened');
  });

  // Widget ready
  widget.on('ready', function() {
    console.log('Widget fully initialized');
  });

  // Errors
  widget.on('error', function(error) {
    console.error('CookieFrame error:', error);
  });

  // Geo fallback (if location detection fails)
  widget.on('geo:fallback', function(data) {
    console.log('Geo detection failed:', data.reason);
    console.log('Using framework:', data.fallbackFramework);
  });

});

Removing Event Listeners

function myHandler(consent) {
  console.log('Consent:', consent);
}

// Add listener
window.CookieFrame.on('consent:updated', myHandler);

// Remove listener
window.CookieFrame.off('consent:updated', myHandler);

Conditional Script Loading

A common use case is loading third-party scripts only after consent is given.

window.CookieFrameQueue = window.CookieFrameQueue || [];
window.CookieFrameQueue.push(function(widget) {
  const consent = widget.getConsent();

  if (consent && consent.analytics) {
    loadGoogleAnalytics();
  }

  if (consent && consent.marketing) {
    loadFacebookPixel();
    loadGoogleAds();
  }

  // Also listen for future consent changes
  widget.on('consent:updated', function(consent) {
    if (consent.analytics) {
      loadGoogleAnalytics();
    }
    if (consent.marketing) {
      loadFacebookPixel();
      loadGoogleAds();
    }
  });
});

function loadGoogleAnalytics() {
  // Check if already loaded
  if (window.gtag) return;

  const script = document.createElement('script');
  script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX';
  script.async = true;
  document.head.appendChild(script);

  window.dataLayer = window.dataLayer || [];
  function gtag(){ dataLayer.push(arguments); }
  window.gtag = gtag;
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXX');
}

Pattern 2: Using Automatic Script Blocking

If you enable Auto Block Scripts in your CookieFrame dashboard, the widget automatically blocks known third-party scripts until consent is given. This works by:

  1. Intercepting scripts as they're added to the page
  2. Checking if they match known tracking patterns (Google Analytics, Facebook Pixel, etc.)
  3. Blocking execution until the appropriate consent category is granted
  4. Automatically enabling scripts when consent is given

This approach requires no custom code—just enable the setting and add your scripts normally.

How Script Blocking Works

CookieFrame uses multiple techniques for maximum browser compatibility:

  • MutationObserver: Watches for new <script> elements added to the page
  • createElement override: Intercepts dynamically created scripts
  • Firefox-specific: Uses the beforescriptexecute event for reliable blocking

When a script is blocked, CookieFrame:

  1. Changes the script's type to text/plain (prevents execution)
  2. Stores the original src in a data-cf-src attribute
  3. Marks it with data-cf-blocked="true"
  4. Restores and executes the script when consent is given

Excluding Scripts from Blocking

Some scripts should never be blocked. Add the data-cookieframe="ignore" attribute:

<!-- These scripts will never be blocked -->
<script src="https://example.com/essential.js" data-cookieframe="ignore"></script>
<script data-cookieframe="ignore">
  // Inline scripts can also be excluded
  criticalFunction();
</script>

CookieFrame also automatically excludes:

  • Google Tag Manager container (googletagmanager.com/gtm.js)
  • gtag.js loader (googletagmanager.com/gtag/js)
  • CookieFrame's own scripts

For Cookiebot migration, CookieFrame also recognizes data-cookieconsent="ignore".

Marking Scripts with Categories

You can explicitly mark scripts with their consent category:

<!-- Block until analytics consent -->
<script src="https://example.com/analytics.js" data-cookieconsent="analytics"></script>

<!-- Block until marketing consent -->
<script src="https://example.com/pixel.js" data-category="marketing"></script>

<!-- Pre-blocked script (already in text/plain format) -->
<script type="text/plain" data-src="https://example.com/tracker.js" data-cookieconsent="marketing"></script>

Valid category values: analytics, marketing, preferences


CookieFrame automatically integrates with Google Consent Mode v2 when enabled in your dashboard. This signals consent status to Google services (Analytics, Ads, Tag Manager).

How It Works

  1. When the page loads, CookieFrame sets default consent state to "denied" for all categories
  2. When a user gives consent, it updates Google's consent state via gtag('consent', 'update', ...)
  3. Google tags automatically adjust their behavior based on consent

Category Mapping

CookieFrame CategoryGoogle Consent Types
Necessarysecurity_storage (always granted)
Analyticsanalytics_storage
Marketingad_storage, ad_user_data, ad_personalization
Preferencesfunctionality_storage, personalization_storage

Basic Mode vs Advanced Mode

Configure this in SettingsIntegrationGoogle Consent Mode v2:

ModeBehavior
Basic Mode (default)No data sent to Google until user consents. Strictest compliance.
Advanced ModeGoogle receives cookieless pings before consent for conversion modeling. Enables better analytics accuracy.

The Wait for update setting (default: 500ms) controls how long Google tags wait for a consent decision before firing with default (denied) values.

  • Lower values (100-500ms): Faster tag firing, but may miss consent if the banner loads slowly
  • Higher values (1000-2000ms): More time for users to respond, recommended for slower sites

Configure this in SettingsIntegrationGoogle Consent Mode v2.

Debugging

Enable Debug Mode in your dashboard to log consent updates to the browser console:

[CookieFrame GCM] setDefaultGoogleConsentMode() called
[CookieFrame GCM] Calling gtag('consent', 'update', {...})
[CookieFrame GCM] gtag consent update called successfully

Storage

CookieFrame stores data in the browser's localStorage:

KeyDescription
cf_consentJSON object with consent preferences
cf_consent_dateISO timestamp of when consent was given
cf_visitor_idUnique visitor UUID (generated once, persists)
cf_tcf_stringTCF TC String (if TCF is enabled)
cf_tcf_consentTCF consent state (if TCF is enabled)

Consent is also synced to CookieFrame's server for:

  • Consent proof/audit logging
  • Cross-device consent (same visitor ID)
  • Dashboard analytics

Consent expires based on the Expire Days setting in your dashboard (default: 365 days). When expired:

  • The banner is shown again
  • Previous consent is not automatically assumed

Debug Mode

Enable debug mode to log detailed information to the browser console. Two ways to enable:

Via URL Parameter

Add ?cf_debug=1 to any page URL:

https://yoursite.com/?cf_debug=1

Via Script Attribute

Add data-debug="true" to the script tag:

<script
  src="https://cookieframe.com/api/widget/YOUR_DOMAIN_ID/script.js"
  data-debug="true"
  async
></script>

Debug output looks like:

[CookieFrame] CookieFrame initializing... {domainId: "A1B2C3D4", visitorId: "...", version: "1.0.0"}
[CookieFrame] Geo detection result: {country: "DE", region: null, framework: "gdpr", ...}
[CookieFrame] Script blocker initialized
[CookieFrame] Restored consent from localStorage: {necessary: true, preferences: true, ...}

Compliance Frameworks

CookieFrame automatically detects the visitor's location and applies the appropriate privacy framework:

FrameworkBehaviorRegions
GDPROpt-in required. Banner shown until user makes a choice.EU/EEA countries
CCPAOpt-out model. "Do Not Sell" option shown.California, USA
NoticeSimple acknowledgment. No consent required.Most other regions

Default Framework

If geo-detection fails, CookieFrame falls back to your configured Default Framework (Settings → Compliance → Default Framework). GDPR is the safe default.

Geo Detection

Location is detected server-side using the visitor's IP address. The widget makes an uncached request to /api/widget/{domainId}/geo on each page load to ensure accurate detection.


Single-Page Applications (SPAs)

For SPAs (React, Vue, Angular, etc.), the widget handles navigation automatically via the History API. However, if you're dynamically adding cookie preferences links after initial load, ensure they have the data-cookieframe="preferences" attribute—CookieFrame watches for new elements via MutationObserver.

React Example

function CookiePreferencesLink() {
  return (
    <button
      data-cookieframe="preferences"
      className="text-sm text-gray-600 hover:underline"
    >
      Cookie Preferences
    </button>
  );
}

Vue Example

<template>
  <a href="#" data-cookieframe="preferences">Cookie Preferences</a>
</template>

No additional JavaScript needed—the widget binds handlers automatically.


Server-Side Rendering (SSR)

CookieFrame works with SSR frameworks like Next.js, Nuxt, and SvelteKit. Add the script tag to your HTML <head> as usual.

Next.js (App Router)

In your root layout (app/layout.tsx):

import Script from 'next/script';

export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        <Script
          src="https://cookieframe.com/api/widget/YOUR_DOMAIN_ID/script.js"
          strategy="afterInteractive"
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

Nuxt 3

In nuxt.config.ts:

export default defineNuxtConfig({
  app: {
    head: {
      script: [
        {
          src: 'https://cookieframe.com/api/widget/YOUR_DOMAIN_ID/script.js',
          async: true
        }
      ]
    }
  }
});

Troubleshooting

  1. Check the Domain ID: Ensure it's exactly 8 characters and uppercase
  2. Check the console: Look for errors starting with [CookieFrame]
  3. Verify the domain matches: The domain in CookieFrame must match your website
  4. Clear localStorage: Remove cf_consent to reset consent state
  5. Check for existing consent: If you've already consented, the banner won't show
  1. Check script order: CookieFrame must load BEFORE other scripts
  2. Enable Auto Block Scripts: In Settings → Behavior
  3. Use conditional loading: Load scripts programmatically after consent
  1. Check localStorage access: Some privacy browsers block localStorage
  2. Check for browser extensions: Ad blockers may interfere
  3. Verify domain configuration: Subdomains are treated as separate domains

Widget Looks Wrong

  1. Check for CSS conflicts: Your site's CSS may override widget styles
  2. Verify styling settings: Check Settings → Design in your dashboard
  3. Test in incognito mode: Extensions can affect rendering

Debug Mode Not Working

  1. Hard refresh: Ctrl+Shift+R or Cmd+Shift+R
  2. Check URL encoding: Use ?cf_debug=1, not ?cf_debug=true
  3. Check script attribute: data-debug="true" (with quotes)

Advanced: Custom Domain ID Attribute

If you need to use a different attribute name (rare), you can also use script tag attributes:

<script
  src="https://cookieframe.com/widget/widget.js"
  data-domain-id="YOUR_DOMAIN_ID"
  data-api-endpoint="https://cookieframe.com/api/widget"
  data-language="de"
  async
></script>
AttributeDescription
data-domain-idYour 8-character Domain ID (required)
data-api-endpointAPI base URL (optional, for self-hosted)
data-languageForce a specific language (e.g., "de", "fr")
data-debugEnable debug mode ("true")

Need Help?

If you're having trouble with manual installation: