UCL Consent JS

A GDPR & ICO Compliant Cookie Consent Library

Demo Notice: This demonstration uses non-functional tracking IDs. No actual user data is collected or transmitted to third-party services. The scripts showcase the consent management system's blocking behavior only.

Content Blocking Demo

These embedded media players demonstrate the consent gate in action. Without consent to 'Embeds', both will be blocked and show consent gates.

SoundCloud Audio Player - Domain allowlisted

Testing Instructions:
  1. First Visit: Consent banner appears YouTube show consent gate and SoundCloud loads.
  2. Open DevTools: Press F12 → Go to Network tab → Reload page
  3. Verify Blocking: No requests to facebook.net, google-analytics.com, hotjar.com, etc.
  4. Test Selective Consent: Open Settings → Enable only "Analytics" → See which services load
  5. Test Full Consent: Enable all categories → Watch Network tab fill with tracking requests

Proactive Blocking

Intercepts and blocks tracking scripts, network requests, and cookies before they execute.

Smart Classification

Multi-layered domain and script classification using known lists, patterns, and content analysis.

Modular Architecture

Core blocking logic and UI are now separate, allowing flexible deployment and lighter page weight.

Proxy Protection

Detects and blocks consent bypasses through CMS proxy endpoints (WordPress, Drupal) whilst maintaining ICO compliance for legitimate content.

Demo Tracking Services

Important: All tracking scripts on this page use non-functional demo IDs. No data is actually collected or transmitted. This demonstrates how the consent management system would classify and block real tracking scripts.

This page loads demo tracking scripts that you can test blocking and unblocking:

Analytics (3 services)
  • Google Analytics (GA4): gtag/js requests
  • Hotjar: hotjar.com script requests
  • Custom Analytics: dataLayer events
Demo Test: Enable "Analytics" consent → Check Network tab (requests use demo IDs only)
Marketing (5 services)
  • Facebook Pixel: connect.facebook.net
  • LinkedIn: snap.licdn.com
  • Twitter/X: static.ads-twitter.com
  • TikTok: analytics.tiktok.com
  • Marketo: munchkin.marketo.net
Demo Test: Enable "Marketing" → Watch for demo script requests (no real tracking)
Embeds (2 services)
  • YouTube: Video embed above
  • SoundCloud: Audio player above
Demo Test: Enable "Embeds" → Media players will load (real content, not tracking)
Quick Verification Commands
// Check what's currently blocked
UCLPrivacy.getStats()
// Shows blocked requests, cookies, scripts
// Test analytics unblocking (demo scripts only!)
UCLPrivacy.updateConsent('analytics', true)
// Demo scripts load but don't collect real data
// Test marketing unblocking (demo scripts only!)
UCLPrivacy.updateConsent('marketing', true)
// Demo scripts load but don't perform real tracking

Live API Demo

Try the consent management API and see the real blocking in action:

Test Specific Categories:

Demo Tip: Keep DevTools Network tab open to watch demo script requests (no real tracking data transmitted)!

Output will appear here...

Deployment

The modular architecture simplifies deployment whilst maintaining security. Add the following to your page's <head> section.

CONFIGURATION (OPTIONAL)
<script> window.privacyConfig = { // Allowlist domains that should never be blocked allowlist: [ 'cdn.yoursite.com', 'api.yoursite.com', 'fonts.googleapis.com' ], // Reclassify specific domains to different categories reclassify: { 'internal-analytics.yoursite.com': 'necessary' }, // Extend built-in domain lists with additional services extend: { analytics: ['matomo.yoursite.com'], marketing: ['custom-tracker.com'], embeds: ['custom-video-platform.com'] }, // Extend cookie classification patterns cookiePatterns: { analytics: ['^custom_analytics_', 'internal_tracking_.*'], marketing: ['^custom_ads_', 'retargeting_.*'] }, // Extend platform display names for blocked content platformMapping: { 'custom-video.yoursite.com': 'Your Custom Video Platform', 'internal-media.yoursite.com': 'Internal Media Service' }, // Extend script content classification patterns scriptPatterns: { analytics: [ { pattern: 'customAnalytics.track', service: 'Custom Analytics' } ], marketing: [ { pattern: 'customAds.fire', service: 'Custom Ad Platform' } ] }, // Settings button position configuration settingsButton: { // Default position for all pages position: { side: 'right', // 'left' or 'right' bottom: '20px', // CSS length (px, rem, vh, vw, em, %, pt) horizontal: '20px', // Distance from left or right edge zIndex: 999997 // Stacking order (1-999999) }, // URL-based conditional positioning urlConditions: [ { pattern: '/admin/*', // Wildcard pattern (* = any characters) position: { side: 'left', // Override side bottom: '60px' // Other properties inherit from default } }, { pattern: '/checkout/*', visibility: { hidden: true // Hide button on checkout pages } } ], // Global visibility toggle visibility: { hidden: false // Set true to disable button entirely } }, // Configuration options options: { // Development and debugging debug: false, // Enable verbose debug logging logging: false, // Log blocking actions to console (default: false) // Blocking behaviour blockingMode: 'strict', // 'strict' or 'balanced' approach defaultUnknownAction: 'block', // How to handle unclassified resources blockFirstPartyInlineScripts: false, // Allow essential CMS patterns (default) blockFirstPartyProxies: true, // Block oEmbed proxy bypasses (default: true) // User interface bannerDelay: 500, // Delay before showing consent banner (ms) enablePlaceholders: true, // Show placeholders for blocked content showBanner: true, // Display consent banner to new users cookiePolicyUrl: './cookie-policy.html' // Link to cookie policy page } }; </script>
SETTINGS BUTTON POSITION CONFIGURATION
window.privacyConfig = { settingsButton: { // Default position for all pages position: { side: 'right', // 'left' or 'right' bottom: '20px', // CSS length (px, rem, vh, vw, em, %, pt) horizontal: '20px', // Distance from left or right edge zIndex: 999997 // Stacking order (1-999999) }, // URL-based conditional positioning urlConditions: [ { pattern: '/admin/*', // Wildcard patterns (* = any characters) position: { side: 'left', // Override side bottom: '60px' // Other properties inherit from default } }, { pattern: '/checkout/*', visibility: { hidden: true // Hide button on checkout pages } }, { pattern: '/node/*/edit', position: { side: 'right', bottom: '80px', horizontal: '30px' } } ], // Global visibility toggle visibility: { hidden: false // Set true to disable button entirely } } };
// Patterns match against URL pathname only (not domain or query string) // Supports * wildcard for any characters (simple glob patterns) // Case-sensitive matching // Examples: '/admin/*' → matches /admin/users, /admin/settings/profile '/*/edit' → matches /posts/123/edit, /pages/about/edit '/checkout' → matches /checkout only (exact match)
// Level 1: System defaults {side: 'right', bottom: '20px', horizontal: '20px', zIndex: 999997} // Level 2: User defaults (merge with system) {side: 'left'} // Result: {side: 'left', bottom: '20px', horizontal: '20px', zIndex: 999997} // Level 3: Matched rule (merge with user defaults) {bottom: '60px'} // Final: {side: 'left', bottom: '60px', horizontal: '20px', zIndex: 999997}
// View effective button configuration for current page const config = UCLPrivacyUI.getEffectiveButtonConfig(); console.log(config.position.side); // 'left' or 'right' console.log(config.hidden); // true or false
Important Limitations:
  • Single-Page Applications (SPAs): Pattern matching happens once at page load. If your site changes URLs without full page reload (React Router, Vue Router, etc.), the button position will not update automatically.
  • Query Strings: Patterns match pathname only. /search?q=test → pattern matches /search, not query parameters.
  • Hash Fragments: Not included in pathname matching. /page#section → pattern matches /page.
  • CSP Requirements: Inline styles with !important require style-src 'unsafe-inline' or appropriate nonces in your Content Security Policy.
OPTION 1: SINGLE FILE DEPLOYMENT (RECOMMENDED)
<script src="https://cookie-consent.ucl.ac.uk/dist/ucl-consent.blocker.min.js"></script>
<script src="https://cookie-consent.ucl.ac.uk/dist/ucl-consent.ui-full.min.js" defer></script>
OPTION 2: SEPARATE CSS DEPLOYMENT
<script src="https://cookie-consent.ucl.ac.uk/dist/ucl-consent.blocker.min.js"></script>
<script src="https://cookie-consent.ucl.ac.uk/dist/ucl-consent.ui-headless.min.js" defer></script>
<link rel="stylesheet" href="https://cookie-consent.ucl.ac.uk/dist/ucl-consent-ui.css">
PROTECTING ESSENTIAL SCRIPTS
<!-- Configuration scripts (automatically detected) --> <script> window.privacyConfig = { /* your config */ }; </script> <!-- Essential functionality scripts --> <script data-privacy-necessary="true"> // Critical site functionality </script> <!-- Additional configuration scripts --> <script data-privacy-config="true"> // Setup or configuration code </script>
INTELLIGENT INLINE SCRIPT HANDLING
// Default behavior (recommended for CMS sites) window.privacyConfig = { options: { blockFirstPartyInlineScripts: false // Allows essential patterns, blocks tracking } }; // Detected patterns that get blocked: // - gtag(), ga(), fbq(), ttq() tracking calls // - Google Analytics, Facebook Pixel, TikTok Pixel // - LinkedIn tracking, Twitter Pixel, Hotjar // Allowed patterns (essential functionality): // - jQuery fallback scripts // - CMS configuration (Drupal.settings, WordPress localization) // - Feature detection and progressive enhancement // - Basic DOM event handlers
OEMBED PROXY PROTECTION
// Default behaviour (recommended for all sites) window.privacyConfig = { options: { blockFirstPartyProxies: true // Prevents consent bypasses through proxy endpoints } }; // Automatically detected proxy patterns for security: // - WordPress: /wp-json/oembed/1.0/proxy // - Drupal: /media/oembed // - Custom CMS: /embed/, /proxy/, /api/oembed // Example blocked URL demonstrating protection: // yourdomain.com/media/oembed?url=https://youtube.com/watch?v=... // Target URL (https://youtube.com) classified as 'embeds' and blocked appropriately // Prevents circumvention of user consent preferences through proxy mechanisms
STRICT SECURITY MODE
// Strict security (requires manual script marking) window.privacyConfig = { options: { blockFirstPartyInlineScripts: true // Blocks all inline scripts by default } }; // Use data attributes to mark essential scripts: <script data-privacy-necessary="true"> // Only marked scripts will execute </script>

Console API Documentation

The modular API is split between the blocker (UCLPrivacy) and UI (UCLPrivacyUI) for better separation of concerns.

UCLPrivacy API (from blocker)
// View current consent state for all categories.
UCLPrivacy.getConsent()
// Returns: {necessary: true, analytics: false, ...}
// Update consent for a single category.
UCLPrivacy.updateConsent('analytics', true)
// Update all non-essential categories simultaneously.
UCLPrivacy.updateAllConsent({analytics: true, marketing: true, embeds: true})
// View comprehensive blocking statistics.
UCLPrivacy.getStats()
// Get count of currently blocked elements.
UCLPrivacy.getBlockedElements()
// Manually trigger content restoration for a specific category.
UCLPrivacy.restoreContent('embeds')
// The UI will listen for this and restore placeholders.
// Access externalized cookie classification patterns.
UCLPrivacy.getCookiePatterns()
// Returns: {analytics: [...], marketing: [...], embeds: [...]}
// Access oEmbed proxy detection patterns.
UCLPrivacy.getProxyPatterns()
// Returns: ['/wp-json/oembed', '/media/oembed', ...]
UCLPrivacyUI API (from UI)
// Open the privacy settings modal programmatically.
UCLPrivacyUI.showSettings()
// Hide the consent banner programmatically.
UCLPrivacyUI.hideBanner()
// Access platform mapping for blocked content placeholders.
UCLPrivacyUI.getPlatformMapping()
// Returns: {'www.youtube.com': 'YouTube', 'soundcloud.com': 'SoundCloud', ...}
Event Listening
// Listen for consent update events.
window.addEventListener('ucl-privacy-consent-updated', (e) => console.log(e.detail))