Key takeaways

A banner records a choice; it does not automatically prevent a script, iframe, pixel, or plugin from running. Effective pre-consent blocking starts earlier in the page lifecycle. Classify each technology by purpose, keep optional code inert or gate it behind the corresponding consent state, and make the default state available before dependent tags can execute.

Updated: 29 August 2026 · Last reviewed: 29 August 2026

For manually embedded scripts, an inert type="text/plain" pattern can work when activation code recreates the script correctly. In Google Tag Manager (GTM), set consent defaults before measurement commands, use Google tags' built-in consent behavior deliberately, and add explicit consent checks to non-Google tags. Replace optional video and map iframes with placeholders until the visitor asks to load them or grants the relevant category.

Test four distinct states in a clean browser: no choice, rejection, category-specific acceptance, and withdrawal. Inspect network traffic as well as cookies and other browser storage. A technically clean result shows that the implementation follows its configured policy; it does not decide whether the policy, category, exemption, or consent interface satisfies every applicable law.

Illustrative state: “unknown” is not depicted as permission. The gate is shown before optional loading paths.

Concept diagram
  1. 01 · Start

    Page begins

    Load the consent state and establish the approved default before dependent optional code.

  2. 02 · Gate

    Route each loading path

    Necessary path

    Available for the requested service

    Available

    Analytics path

    Waits for an analytics choice

    Held

    Marketing path

    Waits for a marketing choice

    Held
  3. 03 · Choice event

    Release only matching paths

    A new or stored choice can change the relevant category state. Keep activation scoped and avoid duplicate loading.

    Observe separately: requests, browser storage, tag firing, and visible controls.
Illustrative implementation flow only. The categories and outcomes shown are examples, not an inventory, measurement result, or legal classification for any site.

What “blocking cookies” actually requires

The phrase is convenient but incomplete. A site can avoid creating a conventional cookie and still read or write localStorage, use IndexedDB, access an existing identifier, send a pixel request, or expose device information to a third party. Deleting cookies after a tag runs cannot undo a request already sent, and it does not clear every other form of device storage or access.

Treat the job as controlling optional technology before execution, not cleaning up afterward. Your inventory should include:

  • first-party and third-party JavaScript;
  • inline scripts and scripts loaded from external URLs;
  • tags deployed through GTM or another tag manager;
  • video, map, social, chat, booking, review, and payment embeds;
  • image pixels, link decoration, and server-set cookies;
  • local storage, session storage, IndexedDB, service workers, and caches; and
  • code injected by themes, plugins, apps, A/B tests, and campaign templates.

Under Article 5(3) of the ePrivacy Directive, the EU baseline concerns storing information on, or gaining access to information stored in, a user's terminal equipment. Consent is generally required unless the operation is solely for transmitting a communication or is strictly necessary to provide a service explicitly requested by the user. Member States implement that rule in national law, and national guidance can differ. GDPR may also govern personal-data processing and the standard for valid consent.

That is the legal assessment. The technical verification asks narrower questions: Did a request occur? Was storage created or read? Did the configured gate respond to the selected category? Engineering evidence informs legal review but does not replace it. Read the ePrivacy Directive explained for the legal framework and obtain jurisdiction-specific advice where needed.

Step 1: Map every technology to a loading rule

Do not begin by editing snippets. First create a deployment map with one row per service or code path.

Field Example question
Owner Which team or supplier added it?
Location GTM, page template, plugin, app, inline code, or iframe?
Trigger Page load, scroll, click, form open, checkout, or login?
Data behavior Which requests, cookies, or other storage can it create or read?
Purpose Security, requested feature, preferences, analytics, or advertising?
Proposed rule Always available, analytics consent, marketing consent, or user-requested load?
Evidence Vendor documentation, network trace, storage trace, and configuration record
Rollback owner Who can disable it if the gate fails?

Separate “necessary” from “convenient”

A category name is not a legal conclusion. Authentication, a shopping basket, load balancing, fraud prevention, or remembering a consent choice may be capable of meeting an applicable necessity test depending on the facts. Analytics or advertising is not made strictly necessary merely because the business relies on its reports.

Have the responsible legal or privacy owner approve the purpose-to-category mapping. Developers should implement that decision and report what the technology actually does. If a vendor changes behavior, reopen both reviews.

Find duplicate deployment paths

The same service often appears twice: GA4 in GTM and in a theme; a Meta pixel in a marketing plugin and a checkout integration; or a YouTube iframe in both reusable components and old articles. Search templates and source for vendor hostnames, tracking IDs, <script, <iframe, new Image, and tag-manager containers. Review runtime requests because generated code will not always appear in the original HTML.

Remove duplicates before adding gates. A perfect GTM rule does not control a hard-coded copy outside GTM.

Step 2: Establish a denied or blocked default early

The default must exist before any optional dependency can run. Loading a CMP at the bottom of the page while a tracker sits in the <head> creates a race the tracker can win.

A robust order is:

  1. initialize the consent state or CMP as early as the integration requires;
  2. expose the default state synchronously where possible;
  3. install interception or gating logic;
  4. load GTM or other dependent loaders;
  5. render the banner; and
  6. release only code allowed by a stored or new choice.

The visible banner can render later than the gate. The important point is that “unknown” is not accidentally treated as “granted.”

Handle returning visitors without a flash of tracking

On a return visit, read the saved preference before optional code starts. Do not briefly grant everything and correct it once the CMP has loaded. If the saved format is invalid, expired, or from an incompatible policy version, use the conservative default required by your approved policy and ask again where appropriate.

Do not trust a client-side cookie solely because it has a familiar name. Validate allowed values and versions. Consent storage should represent the choice; it should not be used as a promise that all downstream tags obeyed it.

Treat ordering as a dependency, not a timeout

Arbitrary delays such as “wait 500 milliseconds, then fire analytics” are fragile. Device speed, cache state, network latency, browser scheduling, and extensions all vary. Prefer an explicit state transition or event:

  • initialize as unknown or denied;
  • resolve a valid stored choice;
  • receive a new banner choice;
  • update the state once; and
  • activate each eligible integration idempotently.

“Idempotently” means repeated consent events do not inject the same script twice. Track activation per integration, not only per category.

Step 3: Gate scripts by category

For a small static site, manually making optional scripts inert is understandable and auditable. For a frequently changing site, central tag management or a tested CMP integration usually reduces scattered code—but no method removes the need to inspect the live result.

Use inert script markup carefully

An unsupported script type is treated as a data block rather than executable JavaScript:

<script
  type="text/plain"
  data-consent-category="analytics"
  data-src="https://example.invalid/analytics.js"
></script>

<script type="text/plain" data-consent-category="analytics">
  window.exampleAnalytics?.start();
</script>

Do not restore the type attribute on an existing element and assume the browser will execute it. Recreate an executable <script> after the category is granted:

const activated = new WeakSet();

function activateCategory(category) {
  const selector = `script[type="text/plain"][data-consent-category="${category}"]`;

  document.querySelectorAll(selector).forEach((blocked) => {
    if (activated.has(blocked)) return;

    const script = document.createElement("script");
    const source = blocked.dataset.src;

    if (source) script.src = source;
    if (blocked.nonce) script.nonce = blocked.nonce;
    if (blocked.hasAttribute("async")) script.async = true;
    if (blocked.hasAttribute("defer")) script.defer = true;
    if (!source) script.textContent = blocked.textContent;

    activated.add(blocked);
    blocked.replaceWith(script);
  });
}

Use real, allowlisted vendor URLs in production; example.invalid above is intentionally non-routable. Preserve attributes your integration needs, including a Content Security Policy nonce, integrity and CORS settings, or ordering semantics. Do not blindly copy every attribute from untrusted markup.

The simple example does not solve dependencies. If an inline configuration command depends on an external library, activate them in a known sequence and handle load failure explicitly. Dynamic single-page application routes also need a controlled way to register newly rendered integrations.

Keep categories independent

Avoid one global hasConsent boolean. A visitor may allow analytics and refuse advertising. Store a structured state and subscribe integrations to only the purpose they need:

const consent = {
  necessary: true,
  preferences: false,
  analytics: false,
  marketing: false,
};

function applyConsent(next) {
  Object.assign(consent, next);
  if (consent.analytics) activateCategory("analytics");
  if (consent.marketing) activateCategory("marketing");
}

Production code also needs schema validation, persistence, policy-version handling, accessibility, and a withdrawal route. Use this snippet to understand the gate, not as a complete consent manager.

Plan for withdrawal

Preventing a not-yet-loaded tool is easier than reversing one already running. On withdrawal:

  1. stop future events and disable the integration using its documented API where available;
  2. update Google consent signals where applicable;
  3. remove optional first-party storage that your own application controls, if that is part of the approved design;
  4. avoid claiming that client-side deletion can retract prior requests, remove third-party server data, or erase every form of device access; and
  5. explain and implement any separate rights process required for downstream personal data.

A reload after withdrawal can help return the page to a known state, but only after the updated preference has been saved and future loading is gated.

Google Consent Mode communicates consent states to Google tags. It is not a universal blocker for every tag in a GTM container, and it does not govern scripts outside that container.

Google's first-party documentation distinguishes two implementations:

  • Basic Consent Mode: Google tags are blocked until the user interacts with the consent mechanism. Before that interaction, no data is sent to Google through those tags.
  • Advanced Consent Mode: Google tags load with consent denied by default and may send cookieless pings. When consent is granted, they use the granted state.

Therefore, “denied” in Advanced mode does not mean “no network request.” Decide which implementation fits the legal and organizational assessment for each region and purpose. A technical test must use the expected behavior for the selected mode rather than assuming zero requests in all configurations. See Google Consent Mode v2 explained for the signal model.

Set defaults before Google configuration commands

For a direct gtag.js implementation, Google's documented pattern initializes the data layer and sets defaults before commands that depend on consent:

<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}

  gtag("consent", "default", {
    ad_storage: "denied",
    ad_user_data: "denied",
    ad_personalization: "denied",
    analytics_storage: "denied"
  });
</script>

After the visitor chooses, send an update before the page or event that needs the new state:

gtag("consent", "update", {
  analytics_storage: "granted",
  ad_storage: "denied",
  ad_user_data: "denied",
  ad_personalization: "denied"
});

For GTM CMP integrations, follow Google's consent-template guidance. Google recommends GTM's consent APIs in custom templates rather than deploying consent commands through Custom HTML tags, because command processing order can produce unreliable timing. Use the Consent Initialization trigger for the CMP template when its official integration instructions call for it, then inspect the container's consent overview.

Add checks to non-Google tags

Tags with built-in consent checks adjust behavior according to the consent types they support. Non-Google Custom HTML and vendor tags do not automatically understand Google's consent state.

For each tag:

  1. open its Consent Settings in GTM;
  2. confirm any built-in checks shown by GTM;
  3. choose Require additional consent for tag to fire where your design requires it;
  4. select the approved consent type or types;
  5. ensure its ordinary trigger cannot bypass the consent requirement; and
  6. test denied, partially granted, granted, and withdrawn states in Preview mode.

Do not add redundant checks to a Google tag without understanding the effect; Google notes that extra consent checks can block tags in ways that interfere with their intended Consent Mode behavior. Conversely, do not interpret “No additional consent required” as proof that a non-Google tag is legally appropriate.

Debug GTM race and trigger failures

In Tag Assistant or GTM Preview, inspect the event sequence and Consent tab. The default should be established before events that can fire controlled tags. An update should reflect the exact categories selected. Common failures include:

  • an All Pages trigger firing before the CMP establishes state;
  • consent updates pushed under the wrong event or data-layer key;
  • a tag using page-view and consent-event triggers, causing duplicate execution;
  • a returning visitor's choice arriving after an early page-view tag;
  • hard-coded scripts bypassing the container; and
  • server-side tagging receiving browser requests that should never have been initiated.

Server-side GTM changes where processing occurs after collection; it does not itself prevent browser storage or the initial browser request.

Step 5: Hold video, map, and social embeds

An iframe can contact its provider as soon as its src loads. Keep an optional embed's URL out of src until the chosen condition is met:

<div class="embed-placeholder" data-embed-category="marketing">
  <p>This video is provided by a third party.</p>
  <button type="button" data-load-embed>Load video</button>
  <template>
    <iframe
      title="Product demonstration"
      src="https://www.youtube-nocookie.com/embed/VIDEO_ID"
      allow="accelerometer; autoplay; encrypted-media; picture-in-picture"
      allowfullscreen
    ></iframe>
  </template>
</div>
document.addEventListener("click", (event) => {
  const button = event.target.closest("[data-load-embed]");
  if (!button) return;

  const placeholder = button.closest(".embed-placeholder");
  const template = placeholder?.querySelector("template");
  if (!template) return;

  placeholder.replaceChildren(template.content.cloneNode(true));
});

This click-to-load example treats the button as a request to load that specific video. Whether that interaction supplies an adequate legal basis and information depends on the applicable rules and design. Alternatively, connect the placeholder to a category grant and activate all matching embeds.

YouTube describes its privacy-enhanced mode as limiting how embedded video views influence the user's browsing experience and says cookies are not set until the user interacts with the player. It is still a third-party embed, and provider behavior can change. Do not describe youtube-nocookie.com as a universal no-tracking or no-consent switch. Apply the same inspection to Maps, Vimeo, social posts, chat, CAPTCHA, booking, and review widgets.

Step 6: Apply the pattern in WordPress and Shopify

Platform integrations add injection points; they do not change the underlying sequence.

WordPress: inventory plugins, theme code, and enqueued scripts

Check the active theme's header, footer, block patterns, widgets, and custom-code areas. Review plugins that add analytics, advertising, optimization, video, forms, or chat. A dequeue rule can stop a script registered through WordPress:

add_action('wp_enqueue_scripts', function () {
    if (!my_site_has_analytics_consent()) {
        wp_dequeue_script('site-analytics');
        wp_deregister_script('site-analytics');
    }
}, 100);

my_site_has_analytics_consent() must be your real, validated integration—not a placeholder copied into production. Server-side PHP sees only the choice included with that request, so a newly granted choice may require client-side activation or navigation. Dequeuing also cannot catch a snippet hard-coded in a template or injected later by a plugin. Prefer documented plugin hooks, keep custom logic in a child theme or site plugin, and retest after plugin and theme updates.

Shopify: use supported privacy and pixel interfaces

Audit theme.liquid, app embeds, custom pixels, customer events, checkout extensions, and installed apps. Shopify provides a Customer Privacy API for consent collection and exposes consent-aware behavior for its pixel environment. Use Shopify's current APIs and each app's documented integration rather than reading an undocumented cookie name or editing platform-managed scripts.

Theme code and app-injected storefront code can still sit outside a pixel's controls. Disable duplicate legacy snippets after migrating, check storefront and checkout separately where you have access, and verify that changing a privacy preference reaches the relevant pixel or app. Shopify's own eligibility and regional behavior settings are implementation inputs, not a legal determination for your store.

Step 7: Verify before and after every choice

Use a fresh browser profile for each scenario. Private mode helps isolate old site data, but privacy protections and extension behavior can differ from an ordinary profile, so repeat important checks in a clean standard profile too.

Run a four-state protocol

  1. No choice: Clear site data, open developer tools before navigation, load a representative page, and do not touch the banner.
  2. Reject optional categories: reject, navigate through several templates, interact with ordinary page features, and reload.
  3. Grant one category at a time: use a fresh state, grant analytics only, then repeat separately for marketing or preferences.
  4. Withdraw: grant a category, confirm expected activation, withdraw it, navigate or reload as designed, and confirm future activity stops.

For every state, record:

  • Network requests, including initiator, request domain, query parameters, response headers, and redirects;
  • cookies under every displayed origin, including first-party cookies written by third-party code;
  • local storage, session storage, IndexedDB, cache storage, and service-worker registrations;
  • GTM Preview consent states and tags fired/not fired;
  • console errors caused by blocked dependencies; and
  • the URL, template, browser, region, login status, timestamp, and exact choice.

Chrome and Edge expose cookies and other storage under Application and requests under Network. Firefox provides Storage Inspector and Network Monitor. Safari's Web Inspector includes Storage and Network after developer features are enabled. Browser interfaces change, so rely on the concepts rather than one screenshot or menu label.

Define expected results before testing

“No _ga cookie” is not enough. In Basic Consent Mode you may expect no Google-tag request before interaction. In Advanced mode, Google's design permits cookieless requests under denied defaults, so document expected request types and parameters from current Google documentation. For a click-to-load video, expect no provider iframe request before activation. For a necessary session cookie, document why it appears and which requested function needs it.

Compare observed results to this expected matrix. Unknown traffic is a finding to investigate, not something to label necessary merely because it appears early.

Use the deeper website cookie audit procedure and consider a free cookie scan to broaden discovery. Automated scans cannot exercise every logged-in state, interaction, geography, or browser and cannot certify compliance.

Step 8: Release safely and keep a rollback path

Consent changes can break measurement, media, checkout dependencies, or the banner itself. Release through staging and a limited deployment where your architecture allows it.

Before deployment:

  • export or version the GTM container and record its published version;
  • keep the prior CMP and category configuration;
  • version application and theme changes;
  • identify a kill switch for each optional integration;
  • make sure essential account, cart, security, and payment paths still work; and
  • assign one person who can authorize rollback.

After deployment, repeat the four-state protocol on production. If optional code leaks before its approved trigger, disable that integration or restore the last known configuration; do not remove the banner while leaving trackers active. If the gate breaks a requested essential flow, roll back the faulty gating change, preserve a conservative state for unrelated optional tools, and investigate the dependency.

A safe rollback is narrow. Reverting the entire consent layer can reintroduce the original leak. Keep independent controls for the banner, consent state, GTM container, manual scripts, and high-risk embeds.

Add regression checks to releases where practical. A browser automation test can start with empty storage, capture requests and storage for each choice, and fail when a denied-domain rule or unexpected storage item appears. Maintain allowlists cautiously: domains and filenames change, and an allowed domain can serve several purposes. Human review remains necessary for newly observed behavior and legal classification.

Technical sign-off can state, with documented test conditions, that selected scripts did not execute, specified requests did not occur, and listed storage was absent before a configured event. It can also show that category choices produced the intended transitions.

Legal sign-off asks different questions: whether consent is required, an exception applies, information is sufficient, choices are freely given and specific, withdrawal is effective, and national rules are met. Browser tools cannot answer those questions. Nor can a clean cookie jar prove that no device access or personal-data processing occurred.

Keep the two records connected: approved purpose and category decisions on one side, versioned technical evidence on the other. Review both whenever a vendor, tag, template, purpose, or jurisdiction changes. For interface and governance considerations, see cookie consent best practices.

Sources

Need a broader baseline before changing tags? Run a free cookie scan →