Install & Google Tag Manager
Consentfolio ships as a single copy-paste block. This page covers where to paste it, why the order matters, and how to confirm it's live.
The embed
Paste this block in the <head>, above any Google Tag Manager or gtag.js snippet, on every page of your site:
<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}
(function(){var ch={};try{var s=localStorage.getItem('cf.consent');if(s){ch=(JSON.parse(s)||{}).choices||{}}}catch(e){}var g=function(k){return ch[k]===true?'granted':'denied'};gtag('consent','default',{analytics_storage:g('analytics'),ad_storage:g('marketing'),ad_user_data:g('marketing'),ad_personalization:g('marketing'),wait_for_update:500});gtag('set','ads_data_redaction',true)})();</script>
<script src="https://js.consentfolio.com/f.js" data-key="YOUR_PUBLIC_KEY" async></script>
Your dashboard's Installation page renders this same block with your project's public key already filled in — copy it from there rather than retyping YOUR_PUBLIC_KEY by hand.
js.consentfolio.com is the forever embed URL. (consentfolio.com/f.js also resolves today, but point your site at js.consentfolio.com — that's the one we commit to long-term.)
Why two tags, and why the order matters
The block is two <script> tags on purpose:
- The first tag is inline and synchronous. It runs immediately, before the browser moves on, and sets Google Consent Mode defaults to
deniedfor every category. Because it's inline, it can guarantee those defaults are in place before GTM orgtag.jsever evaluates — which is exactly what Consent Mode v2 requires. - The second tag (
f.js) isasync. Async scripts execute in whatever order they finish downloading, sof.jsalone can't guarantee it runs before GTM. That's why the defaults live in the small inline stub instead.
This is also why the whole block must sit above your GTM container snippet or gtag.js load: if GTM loads first, it can fire tags before Consent Mode defaults are set.
Fail-closed by design: if an ad blocker prevents f.js from loading at all, the inline stub has already set every category to denied, and no dashboard-configured or data-cf-consent-marked script becomes active. The result is lossy for your analytics (no banner, no consent, so nothing beyond necessary runs) — it is never a compliance gap.
Verifying the install is live
Your project's Installation page in the dashboard shows an install-health chip driven by real config-fetch traffic from your site:
- "Not detected yet" — no config request has been seen from your domain yet. Check that the block is on the page and that your domain is registered on the project (the config endpoint is domain-locked; see below).
- "Installed — last seen X ago" —
f.jsis fetching config normally. - "Stale — no fetches in 48h" (amber) — it was working, but nothing has been seen in the last two days; worth checking for a recent template change or removal.
Requests from localhost and 127.0.0.1 are excluded from this chip, so local development won't show as "installed."
Domain lock
f.js fetches its config from js.consentfolio.com with your project's public key. That request only succeeds if the page's origin matches your project's registered domain (or a subdomain of it) — apex, www, and any *.yourdomain.com all ride on one registration. localhost and 127.0.0.1 are always allowed, so you can test the embed locally before your domain is registered or before DNS/deployment is finished.