How to check if consent mode is enabled
Load the page with the network log open and read the requests, not the configuration. A default consent state has to be pushed before the first tag call. On the fixture below, the page that pushed it late still sent a hit with analytics_storage=unset and set a cookie, while window.dataLayer looked correct afterwards.
Why check this
Run this on staging before every release that touches tags, the consent banner or the tag manager container, and after anyone edits the container outside the release. The question is narrow: what do the tags do in the window between the first byte and the visitor's decision.
The failure it catches is a default that arrives too late. The snippet is present, the tag manager reports consent mode as configured, and the measurement hit has already gone out with a cookie attached. A reader of window.dataLayer after load sees the consent entry and calls the check passed. The request that left before it is the evidence that matters.
Prerequisites
- Node 22 with
puppeteer-core, and curl 8.21 or later. - A fixture with three pages: a default pushed before the tag, no default at all, and a default pushed after the tag. The tag here is a local stub of two lines, so nothing leaves the machine and nothing is sent to Google. It exists to give the check something to read; on your own site you run the same steps against your real tag. Save as
consent.mjsand start it withnode consent.mjs.
import { createServer } from 'node:http';
const BOOT = '<script>window.dataLayer=[];function gtag(){dataLayer.push(arguments);}</script>';
const DEF = "<script>gtag('consent','default',{ad_storage:'denied',analytics_storage:'denied',"
+ "ad_user_data:'denied',ad_personalization:'denied'});</script>";
const TAG = '<script src="/tag.js"></script>';
const PAGES = { '/no-default': BOOT + TAG, '/with-default': BOOT + DEF + TAG, '/late-default': BOOT + TAG + DEF };
const TAGJS = "var c=(window.dataLayer||[]).filter(function(e){return e[0]==='consent'&&e[1]==='default';}).pop();"
+ "gtag('config','FIXTURE-1');new Image().src='/collect?analytics_storage='+(c?c[2].analytics_storage:'unset');";
const t0 = Date.now();
createServer((req, res) => {
const [path, qs] = req.url.split('?');
const granted = !/analytics_storage=denied/.test(qs || '');
if (path === '/collect' && granted) res.setHeader('set-cookie', '_fx=8f2c; Path=/');
console.log(`+${String(Date.now() - t0).padStart(4)}ms ${path}${qs ? '?' + qs : ''}`
+ (path === '/collect' ? (granted ? ' -> Set-Cookie: _fx=8f2c' : ' -> no cookie') : ''));
if (path === '/tag.js') { res.setHeader('content-type', 'text/javascript'); return res.end(TAGJS); }
if (path === '/collect') { res.setHeader('content-type', 'image/gif'); return res.end(); }
res.setHeader('content-type', 'text/html; charset=utf-8');
res.end(`<!doctype html><title>${path}</title>${PAGES[path] || '<p>unknown</p>'}`);
}).listen(9644, '127.0.0.1', () => console.log('consent fixture on http://127.0.0.1:9644/'));
- A probe that loads one page and prints the data layer, the requests and the cookies. Save as
consentprobe.mjs:
import { launch } from 'puppeteer-core';
const path = process.argv[2];
const browser = await launch({
executablePath: 'C:/Program Files/Google/Chrome/Application/chrome.exe',
headless: true,
});
const page = await browser.newPage();
const seen = [];
page.on('request', (r) => seen.push(new URL(r.url()).pathname + new URL(r.url()).search));
await page.goto(`http://127.0.0.1:9644${path}`, { waitUntil: 'networkidle2' });
const dl = await page.evaluate(() =>
(window.dataLayer || []).map((e, i) => `${i}: ${JSON.stringify(Array.from(e))}`));
console.log(`${path} ${await browser.version()}`);
console.log(`dataLayer\n${dl.join('\n') || '(empty)'}`);
console.log(`requests ${seen.join(' ')}`);
console.log(`cookies ${(await page.cookies()).map((c) => c.name + '=' + c.value).join(' ') || '(none)'}`);
await browser.close();
- Google's own rule for ordering: set the default "before any commands that send measurement data (such as
configorevent)", from the consent mode guide. - In Git Bash, prefix the probe with
MSYS_NO_PATHCONV=1, or the leading slash in the page argument is rewritten into a Windows path and the browser reports an invalid URL.
Steps
- Step 1.
Read the order the markup declares, which is the cheapest check and the one that lies most often.
for p in no-default with-default late-default; do echo "/$p"; curl -s http://127.0.0.1:9644/$p | grep -o -E "consent','default'|src=\"/tag.js\""; done/no-default src="/tag.js" /with-default consent','default' src="/tag.js" /late-default src="/tag.js" consent','default'Two of the three have a default. Only one declares it above the tag. Markup order answers nothing when the consent call is injected by a tag manager, which is why the next steps read the wire.
- Step 2.
Load the page with no default at all.
MSYS_NO_PATHCONV=1 node consentprobe.mjs /no-default/no-default Chrome/152.0.7977.76 dataLayer 0: ["config","FIXTURE-1"] requests /no-default /tag.js /collect?analytics_storage=unset /favicon.ico cookies _fx=8f2cNo consent entry in the data layer, a hit on the wire, and a cookie. This is the baseline shape of a page with consent mode absent.
- Step 3.
Load the page that sets the default first.
MSYS_NO_PATHCONV=1 node consentprobe.mjs /with-default/with-default Chrome/152.0.7977.76 dataLayer 0: ["consent","default",{"ad_storage":"denied","analytics_storage":"denied","ad_user_data":"denied","ad_personalization":"denied"}] 1: ["config","FIXTURE-1"] requests /with-default /tag.js /collect?analytics_storage=denied /favicon.ico cookies (none)The request still goes out. What changed is its content and the absence of the cookie. Google's guide states the same for its own tags: with
ad_storagedenied, "new cookies won't be set for advertising purposes" while "Data sent to Google will still include the full page URL". - Step 4.
Load the page that sets the same default one line too late.
MSYS_NO_PATHCONV=1 node consentprobe.mjs /late-default/late-default Chrome/152.0.7977.76 dataLayer 0: ["config","FIXTURE-1"] 1: ["consent","default",{"ad_storage":"denied","analytics_storage":"denied","ad_user_data":"denied","ad_personalization":"denied"}] requests /late-default /tag.js /collect?analytics_storage=unset /favicon.ico cookies _fx=8f2cThe consent default is in the data layer, so a check that searches the array for a consent entry passes. The hit left with
unsetand the cookie was written. Only the request line separates this page from step 3. - Step 5.
Read the fixture's terminal for the order and the timing on the server side.
consent fixture on http://127.0.0.1:9644/ +2852ms /no-default +2870ms /tag.js +2876ms /collect?analytics_storage=unset -> Set-Cookie: _fx=8f2c +2881ms /favicon.ico +5640ms /with-default +5658ms /tag.js +5666ms /collect?analytics_storage=denied -> no cookie +5672ms /favicon.ico +7914ms /late-default +7930ms /tag.js +7937ms /collect?analytics_storage=unset -> Set-Cookie: _fx=8f2c +7942ms /favicon.icoEvery hit landed within 30 ms of the document, long before a banner could be answered. On localhost there is no network in those numbers, so read them as ordering, not as latency.
How to read the result
| What you see | What it means | What to do | | --- | --- | --- | | No consent entry in the data layer | Consent mode is not configured on this page | Add the default before the tag loads, then rerun steps 2 to 4. | | Consent entry present, hit carries a denied state | The default was applied in time | Check the update call after the banner is answered as well. | | Consent entry present, hit carries no state | The default arrived after the tag fired | Move it above the tag, or into the container's initialisation. | | Cookie written before any decision | Storage happened without consent | Open the response that set it and trace the tag that asked for it. |
Common mistakes
What to check next
- How to check if tracking scripts load before consent: the same window, measured by which scripts execute.
- How to check if cookies are set without consent: the cookie side of the hit in step 2.
- How to test cookie consent banner: what the banner does once the visitor answers it.
- How to check a consent record: whether the decision was stored with a scope and a timestamp.
FAQ
How do I check google consent mode v2?
The four consent types are ad_storage, ad_user_data, ad_personalization and analytics_storage. Confirm all four appear in the default call, then measure a page load and read the consent state carried by the tag's own request.
Is there a consent mode checker?
Tag Assistant and the browser network panel both show the calls. The steps above produce the same answer as text output you can diff between builds and attach to a ticket, which a screenshot of a debug panel does not.
Where should the default consent call go?
Before any command that sends measurement data. Step 4 shows a default one line below the tag, which left the data layer looking correct and the hit already sent.
Does consent mode block requests?
No. In step 3 the denied default changed the request and stopped the cookie. Treat "no requests" as an expectation to verify against your own tag rather than as the defined behaviour.
Verified
Verified by Maks Vernycurl 8.21.0Node 22.23.2Chrome 152.0.7977.76
Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.
Related on this site
intermediate12 minpublished updated Maks Verny