Under GDPR and the ePrivacy Directive, tracking scripts must not execute until the user has actively given consent for the relevant cookie category. This means that simply showing a cookie banner is not enough — you need to technically prevent scripts from running until consent is granted.
CookieBeam provides a built-in mechanism called script blocking (also called script management) that lets you do exactly this. By adding two HTML attributes to your script tags, CookieBeam will hold them dormant and only activate them after the user accepts the matching cookie category.
Using Google Tag Manager?
If all your tracking scripts are loaded through Google Tag Manager and you have enabled Consent Mode V2, GTM already gates script execution based on consent signals. You do not need manual script blocking for GTM-managed tags. This guide is for scripts that are hardcoded directly in your HTML, outside of GTM.
How Script Blocking Works
CookieBeam's script blocking works by exploiting how browsers handle the type attribute on <script> tags. When a script has type="text/plain" instead of type="text/javascript", the browser treats it as a data block and does not execute it.
CookieBeam scans the page for all script tags marked with a data-category attribute. When a user gives consent for that category, CookieBeam clones the dormant script tag, restores its original type and source, and inserts it back into the page — causing the browser to execute it normally.
The Blocking Lifecycle
Page loads with dormant scripts
Your script tags have type="text/plain" and data-category="...". The browser sees them but does not execute them.
CookieBeam initializes
The CookieBeam banner script scans the DOM and finds all <script data-category="..."> elements, recording which category each belongs to.
User interacts with the cookie banner
The user clicks "Accept All" or selects specific categories in the preferences modal.
Consented scripts are activated
CookieBeam replaces each dormant script that belongs to an accepted category with a fresh, executable copy. External scripts are loaded sequentially to maintain dependency order.
Required Attributes
To block a script, you need exactly two attributes:
| Attribute | Required | Description |
|---|---|---|
| type="text/plain" | Yes | Prevents the browser from executing the script. CookieBeam restores the original type when consent is given. |
| data-category="..." | Yes | Tells CookieBeam which cookie category this script belongs to: necessary, analytics, marketing, or preferences. |
| data-src="..." | For external scripts | Use instead of src for external scripts. Prevents the browser from fetching the file before consent. |
| data-service="..." | Optional | Gives the script a human-readable name that appears as a toggle in the preferences modal, letting users control it individually. |
| data-type="..." | Optional | Set a custom script type (e.g. "module") that CookieBeam will restore when the script is activated. |
Basic Example: Inline Script
Here is the simplest case — an inline script that should only run when the user consents to analytics cookies:
1 <!-- Before: this script runs immediately on page load --> 2 <script> 3 console.log('Tracking user...'); 4 </script> 5 6 <!-- After: blocked until user accepts "analytics" --> 7 <script type="text/plain" data-category="analytics"> 8 console.log('Tracking user...'); 9 </script>
External Scripts: Use data-src
For external scripts that load a file via the src attribute, you must replace src with data-src. This prevents the browser from fetching the file before consent is given — even though type="text/plain" stops execution, many browsers still download the resource when src is present.
1 <!-- Before: browser downloads and executes immediately --> 2 <script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script> 3 4 <!-- After: not even downloaded until consent --> 5 <script type="text/plain" data-category="analytics" 6 data-src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"> 7 </script>
Always use data-src for external scripts
If you keep the src attribute, the browser may still download the script file (even though it won't execute it). This can trigger network requests to third-party servers before consent, which some Data Protection Authorities consider a GDPR violation. Always replace src with data-src.
Cookie Categories
CookieBeam uses four standard cookie categories. Use the exact lowercase string in the data-category attribute:
| Category | data-category value | Typical scripts | Consent required? |
|---|---|---|---|
| Necessary | necessary | Login, security tokens, payment processing, reCAPTCHA | No — always active. No tagging needed. |
| Analytics | analytics | Google Analytics, Hotjar, Mixpanel, Plausible, PostHog, Amplitude | Yes |
| Marketing | marketing | Facebook Pixel, Google Ads, LinkedIn Insight, TikTok Pixel, Twitter Pixel, Criteo | Yes |
| Preferences | preferences | Chat widgets (Intercom, Drift), language preferences, UI customisation | Yes |
Ready-to-Use Examples
Below are copy-paste examples for the most commonly used tracking scripts. Replace the placeholder IDs with your own.
Google Analytics (GA4)
1 <!-- Google Analytics GA4 --> 2 <script type="text/plain" data-category="analytics" 3 data-service="Google Analytics" 4 data-src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"> 5 </script> 6 <script type="text/plain" data-category="analytics" 7 data-service="Google Analytics"> 8 window.dataLayer = window.dataLayer || []; 9 function gtag(){dataLayer.push(arguments);} 10 gtag('js', new Date()); 11 gtag('config', 'G-XXXXXXX'); 12 </script>
Facebook Pixel
1 <!-- Facebook Pixel --> 2 <script type="text/plain" data-category="marketing" 3 data-service="Facebook Pixel"> 4 !function(f,b,e,v,n,t,s) 5 {if(f.fbq)return;n=f.fbq=function(){n.callMethod? 6 n.callMethod.apply(n,arguments):n.queue.push(arguments)}; 7 if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0'; 8 n.queue=[];t=b.createElement(e);t.async=!0; 9 t.src=v;s=b.getElementsByTagName(e)[0]; 10 s.parentNode.insertBefore(t,s)}(window, document,'script', 11 'https://connect.facebook.net/en_US/fbevents.js'); 12 fbq('init', 'YOUR_PIXEL_ID'); 13 fbq('track', 'PageView'); 14 </script>
Hotjar
1 <!-- Hotjar --> 2 <script type="text/plain" data-category="analytics" 3 data-service="Hotjar"> 4 (function(h,o,t,j,a,r){ 5 h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)}; 6 h._hjSettings={hjid:YOUR_HJID,hjsv:6}; 7 a=o.getElementsByTagName('head')[0]; 8 r=o.createElement('script');r.async=1; 9 r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv; 10 a.appendChild(r); 11 })(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv='); 12 </script>
LinkedIn Insight Tag
1 <!-- LinkedIn Insight Tag --> 2 <script type="text/plain" data-category="marketing" 3 data-service="LinkedIn Insight"> 4 _linkedin_partner_id = "YOUR_PARTNER_ID"; 5 window._linkedin_data_partner_ids = 6 window._linkedin_data_partner_ids || []; 7 window._linkedin_data_partner_ids.push(_linkedin_partner_id); 8 </script> 9 <script type="text/plain" data-category="marketing" 10 data-service="LinkedIn Insight" 11 data-src="https://snap.licdn.com/li.lms-analytics/insight.min.js"> 12 </script>
TikTok Pixel
1 <!-- TikTok Pixel --> 2 <script type="text/plain" data-category="marketing" 3 data-service="TikTok Pixel"> 4 !function (w, d, t) { 5 w.TiktokAnalyticsObject=t; 6 var ttq=w[t]=w[t]||[]; 7 ttq.methods=["page","track","identify","instances", 8 "debug","on","off","once","ready","alias", 9 "group","enableCookie","disableCookie"]; 10 ttq.setAndDefer=function(t,e){ 11 t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}; 12 for(var i=0;i<ttq.methods.length;i++) 13 ttq.setAndDefer(ttq,ttq.methods[i]); 14 ttq.instance=function(t){ 15 for(var e=ttq._i[t]||[],n=0;n<ttq.methods.length;n++) 16 ttq.setAndDefer(e,ttq.methods[n]);return e}; 17 ttq.load=function(e,n){ 18 var i="https://analytics.tiktok.com/i18n/pixel/events.js"; 19 ttq._i=ttq._i||{};ttq._i[e]=[];ttq._i[e]._u=i; 20 ttq._t=ttq._t||{};ttq._t[e]=+new Date; 21 ttq._o=ttq._o||{};ttq._o[e]=n||{}; 22 var o=document.createElement("script"); 23 o.type="text/javascript";o.async=!0;o.src=i+"?sdkid="+e+"&lib="+t; 24 var a=document.getElementsByTagName("script")[0]; 25 a.parentNode.insertBefore(o,a)}; 26 ttq.load('YOUR_PIXEL_ID'); 27 ttq.page(); 28 }(window, document, 'ttq'); 29 </script>
Google Ads Conversion Tracking
1 <!-- Google Ads Conversion Tracking --> 2 <script type="text/plain" data-category="marketing" 3 data-service="Google Ads" 4 data-src="https://www.googletagmanager.com/gtag/js?id=AW-XXXXXXX"> 5 </script> 6 <script type="text/plain" data-category="marketing" 7 data-service="Google Ads"> 8 window.dataLayer = window.dataLayer || []; 9 function gtag(){dataLayer.push(arguments);} 10 gtag('js', new Date()); 11 gtag('config', 'AW-XXXXXXX'); 12 </script>
Per-Service Toggles
When you add the data-service attribute, CookieBeam automatically generates a toggle in the preferences modal that lets users enable or disable that specific service within its category. For example, a user might accept the "analytics" category overall but disable Hotjar specifically.
All scripts with the same data-service name are grouped together — they are activated or deactivated as a unit.
When to use data-service
Use data-service when you have multiple tracking scripts in the same category and want to give users fine-grained control. For example, if you use both Google Analytics and Hotjar (both analytics), adding data-service to each lets users keep one while disabling the other.
Running Cleanup Scripts on Consent Withdrawal
Sometimes you need to run cleanup logic when a user withdraws consent for a category — for example, disabling a Google Analytics property or removing event listeners. You can do this by prefixing the category name with !:
1 <!-- This runs when user ACCEPTS analytics --> 2 <script type="text/plain" data-category="analytics"> 3 gtag('config', 'G-XXXXXXX'); 4 </script> 5 6 <!-- This runs when user DISABLES analytics --> 7 <script type="text/plain" data-category="!analytics"> 8 window['ga-disable-G-XXXXXXX'] = true; 9 document.cookie = '_ga=; Max-Age=0; path=/;'; 10 document.cookie = '_ga_XXXXXXX=; Max-Age=0; path=/;'; 11 </script>
Disable scripts run only once
A data-category="!analytics" script can only fire once — when the user actively switches the category off. If you need logic that runs every time consent changes (e.g. the user toggles analytics on and off multiple times), use the onChange callback instead of script tag blocking.
Script Blocking vs Google Consent Mode
It is important to understand the difference between these two approaches, because they solve the same problem in different ways:
Script Blocking vs Consent Mode
| Aspect | Script Blocking (data-category) | Google Consent Mode V2 |
|---|---|---|
| How it works | Prevents the script from executing entirely until consent | Lets the script load but sends consent signals so Google tags self-regulate |
| What scripts support it | Any script — works with all vendors | Only Google tags (GA4, Google Ads, Floodlight) |
| Data sent before consent | None — script does not even load | Cookieless pings are sent (no PII, used for conversion modeling) |
| Best for | Non-Google scripts (Facebook, Hotjar, TikTok, etc.) | Google ecosystem tags loaded via GTM |
| Setup | Edit HTML: add type and data-category to each script | Automatic when using CookieBeam GTM template |
Can I use both?
Yes — and you should. Use Google Consent Mode V2 for your Google tags (loaded through GTM), and use script blocking for any non-Google scripts hardcoded in your HTML. This gives you the best of both worlds: Google's cookieless conversion modeling plus strict blocking for everything else.
Troubleshooting
Script still fires before consent
Check that you have both required attributes: type="text/plain" and data-category="...". Missing either one means the browser will execute the script normally. Also make sure the CookieBeam banner script loads before the blocked scripts in your HTML.
External script file is still downloaded
Replace src with data-src. Even with type="text/plain", some browsers will still fetch the file from the server if src is present — it just won't execute it.
Script does not activate after consent
Verify the data-category value matches one of the configured categories exactly (lowercase): necessary, analytics, marketing, or preferences. A typo or different casing will cause CookieBeam to ignore the script.
Multiple scripts depend on each other
CookieBeam loads external scripts sequentially in DOM order. If script B depends on script A, make sure script A appears first in your HTML. Inline scripts execute immediately after replacement, before the next external script starts loading.
Checklist Before Going Live
Script Blocking Checklist
Every non-essential script has type="text/plain" and data-category
Search your HTML source for <script src= tags that don't have these attributes
External scripts use data-src instead of src
Prevents the browser from fetching the file before consent
CookieBeam banner script loads first
Place it before any blocked scripts in <head> so it can intercept them
Test with a fresh browser (no cookies)
Clear all cookies and localStorage, then reload — no tracking requests should appear in DevTools Network tab
Test consent flow end-to-end
Accept cookies and verify blocked scripts activate — check the Network tab for the expected requests
Test reject flow
Reject all cookies and verify no tracking requests are made on subsequent page loads
Verify Google Consent Mode still works
If you use GTM, check that consent default and update commands still fire correctly alongside script blocking