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:
- A CookieFrame account with your domain added
- Access to edit your website's HTML (specifically the
<head>section) - Your 8-character Domain ID from the CookieFrame Dashboard
Finding Your Domain ID
- Log in to your CookieFrame Dashboard
- Select your domain from the list
- Go to Settings → Integration
- 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:
- Fetches configuration from the pre-baked script (no additional API call needed)
- Detects visitor location to determine the applicable privacy framework (GDPR, CCPA, or Notice)
- Checks for existing consent in localStorage and on the server
- Shows the consent banner if no valid consent exists
- Initializes script blocking if enabled in your settings
- 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.
Cookie Preferences Link
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.
Option 1: Data Attribute (Recommended)
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 Consent Status
// 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 falseProgrammatic Consent
// 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();Consent Categories
CookieFrame uses four standard categories:
| Category | Key | Description |
|---|---|---|
| Necessary | necessary | Essential for website functionality. Always enabled, cannot be disabled. |
| Preferences | preferences | Remember user preferences like language or theme. |
| Analytics | analytics | Track website usage for performance insights. |
| Marketing | marketing | Enable 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.
Pattern 1: Check Consent Before Loading
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:
- Intercepting scripts as they're added to the page
- Checking if they match known tracking patterns (Google Analytics, Facebook Pixel, etc.)
- Blocking execution until the appropriate consent category is granted
- 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
beforescriptexecuteevent for reliable blocking
When a script is blocked, CookieFrame:
- Changes the script's
typetotext/plain(prevents execution) - Stores the original
srcin adata-cf-srcattribute - Marks it with
data-cf-blocked="true" - 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
Google Consent Mode v2
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
- When the page loads, CookieFrame sets default consent state to "denied" for all categories
- When a user gives consent, it updates Google's consent state via
gtag('consent', 'update', ...) - Google tags automatically adjust their behavior based on consent
Category Mapping
| CookieFrame Category | Google Consent Types |
|---|---|
| Necessary | security_storage (always granted) |
| Analytics | analytics_storage |
| Marketing | ad_storage, ad_user_data, ad_personalization |
| Preferences | functionality_storage, personalization_storage |
Basic Mode vs Advanced Mode
Configure this in Settings → Integration → Google Consent Mode v2:
| Mode | Behavior |
|---|---|
| Basic Mode (default) | No data sent to Google until user consents. Strictest compliance. |
| Advanced Mode | Google receives cookieless pings before consent for conversion modeling. Enables better analytics accuracy. |
Wait for Consent Update
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 Settings → Integration → Google 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 successfullyStorage
CookieFrame stores data in the browser's localStorage:
| Key | Description |
|---|---|
cf_consent | JSON object with consent preferences |
cf_consent_date | ISO timestamp of when consent was given |
cf_visitor_id | Unique visitor UUID (generated once, persists) |
cf_tcf_string | TCF TC String (if TCF is enabled) |
cf_tcf_consent | TCF 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 Expiration
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=1Via 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:
| Framework | Behavior | Regions |
|---|---|---|
| GDPR | Opt-in required. Banner shown until user makes a choice. | EU/EEA countries |
| CCPA | Opt-out model. "Do Not Sell" option shown. | California, USA |
| Notice | Simple 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
Banner Doesn't Appear
- Check the Domain ID: Ensure it's exactly 8 characters and uppercase
- Check the console: Look for errors starting with
[CookieFrame] - Verify the domain matches: The domain in CookieFrame must match your website
- Clear localStorage: Remove
cf_consentto reset consent state - Check for existing consent: If you've already consented, the banner won't show
Scripts Still Load Before Consent
- Check script order: CookieFrame must load BEFORE other scripts
- Enable Auto Block Scripts: In Settings → Behavior
- Use conditional loading: Load scripts programmatically after consent
Consent Not Persisting
- Check localStorage access: Some privacy browsers block localStorage
- Check for browser extensions: Ad blockers may interfere
- Verify domain configuration: Subdomains are treated as separate domains
Widget Looks Wrong
- Check for CSS conflicts: Your site's CSS may override widget styles
- Verify styling settings: Check Settings → Design in your dashboard
- Test in incognito mode: Extensions can affect rendering
Debug Mode Not Working
- Hard refresh: Ctrl+Shift+R or Cmd+Shift+R
- Check URL encoding: Use
?cf_debug=1, not?cf_debug=true - 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>| Attribute | Description |
|---|---|
data-domain-id | Your 8-character Domain ID (required) |
data-api-endpoint | API base URL (optional, for self-hosted) |
data-language | Force a specific language (e.g., "de", "fr") |
data-debug | Enable debug mode ("true") |
Need Help?
If you're having trouble with manual installation:
- Email us at support@cookieframe.com
- Check our FAQ for common questions
- Try the GTM integration as an alternative