How to Build a Cookie Consent Banner in WordPress Without a Plugin

Published On: March 16th, 2026|Categories: WordPress|10 min read|

Cookie consent banners are no longer optional on most sites that serve EU visitors. The GDPR, ePrivacy Directive, and similar laws in the UK, Brazil, and Canada all require informed, prior consent before setting non-essential cookies – and a banner shoved into the footer with no real opt-out mechanism does not qualify.

The good news is that rolling a compliant banner without a plugin is about 80 lines of code. No dependency bloat, no third-party script loading 200 KB of consent-management JS, no weekly “consent platform” subscription.

What the Banner Actually Needs to Do

Before writing a single line, pin down the functional requirements. A compliant banner must present the user with a clear choice, record that choice, and act on it before any non-essential cookies fire. This means the banner must appear before analytics or marketing scripts initialize – not after.

The banner needs to store the user’s decision in a cookie (or localStorage, though a cookie is easier to read server-side). It must respect that decision on every subsequent page load. It must offer an actual way to decline – not just an “Accept” button with a tiny “X” that closes the banner without registering a rejection.

For a WooCommerce store, the distinction between essential and non-essential cookies matters. WooCommerce sets several functional cookies that are strictly necessary for cart and session handling – those do not require consent. Analytics, Facebook Pixel, and Google Ads cookies absolutely do.

A minimal but defensible implementation splits cookies into three buckets: necessary (always on), analytics (opt-in), and marketing (opt-in). If the site only uses Google Analytics and no ad platform, two buckets are enough. The consent cookie itself stores a JSON payload recording what the user accepted, plus the timestamp.

// Store consent as a JSON string in a first-party cookie
// Example payload: {"necessary":true,"analytics":true,"marketing":false,"ts":1710000000}

WordPress does not need to do much here server-side, but reading the consent cookie in PHP lets you conditionally enqueue scripts. Hook into wp_enqueue_scripts and check the cookie before loading any analytics code.

add_action( 'wp_enqueue_scripts', 'wrt_maybe_enqueue_analytics' );
function wrt_maybe_enqueue_analytics() {
    $consent_raw = isset( $_COOKIE['wrt_consent'] ) ? $_COOKIE['wrt_consent'] : '';
    $consent     = $consent_raw ? json_decode( stripslashes( $consent_raw ), true ) : [];

    if ( ! empty( $consent['analytics'] ) ) {
        wp_enqueue_script(
            'gtag',
            'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX',
            [],
            null,
            true
        );
    }
}

This approach keeps analytics scripts off the page entirely until consent exists. That is meaningfully different from loading the script and then calling a “deny” method on it – the latter still fires a network request to Google.

Because the $_COOKIE superglobal is available on the first request (the browser sends cookies with every request), the server can make this decision before the HTML response is even assembled. Controlling script loading via functions.php keeps the implementation contained and easy to audit.

The banner itself is pure HTML and JavaScript, output via a wp_footer hook. No jQuery dependency needed – the consent interaction is simple enough to handle with 40 lines of vanilla JS.

(function () {
  var COOKIE_NAME = 'wrt_consent';
  var COOKIE_DAYS = 365;

  function getCookie(name) {
    var match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));
    return match ? JSON.parse(decodeURIComponent(match[1])) : null;
  }

  function setCookie(name, value, days) {
    var expires = new Date();
    expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000);
    document.cookie = name + '=' + encodeURIComponent(JSON.stringify(value))
      + ';expires=' + expires.toUTCString()
      + ';path=/;SameSite=Lax;Secure';
  }

  var existing = getCookie(COOKIE_NAME);
  if (existing) return; // already consented or declined

  var banner = document.getElementById('wrt-cookie-banner');
  if (!banner) return;
  banner.style.display = 'flex';

  document.getElementById('wrt-accept-all').addEventListener('click', function () {
    setCookie(COOKIE_NAME, { necessary: true, analytics: true, marketing: true, ts: Date.now() }, COOKIE_DAYS);
    banner.remove();
    location.reload(); // reload so PHP-gated scripts can fire
  });

  document.getElementById('wrt-accept-necessary').addEventListener('click', function () {
    setCookie(COOKIE_NAME, { necessary: true, analytics: false, marketing: false, ts: Date.now() }, COOKIE_DAYS);
    banner.remove();
  });
})();

The location.reload() on accept is a pragmatic choice. After consent is granted the browser needs to send the new cookie to the server so PHP can conditionally enqueue analytics scripts on the next page load. Without the reload, a user who just accepted will not see analytics fire until they navigate. Some implementations skip the reload and push the GA init directly from JS – both approaches are valid, but the PHP-gated version is cleaner for multi-script setups.

Outputting the Banner HTML from PHP

Put the HTML in a footer hook rather than hardcoding it in a template file. This makes it easy to toggle via a theme option or a simple define constant in wp-config.php.

add_action( 'wp_footer', 'wrt_cookie_banner_html' );
function wrt_cookie_banner_html() {
    if ( isset( $_COOKIE['wrt_consent'] ) ) return;
    ?>
    <div id="wrt-cookie-banner" style="position:fixed;bottom:0;left:0;right:0;background:#1a1a1a;color:#fff;padding:1rem 1.5rem;z-index:99999;justify-content:space-between;align-items:center;gap:1rem;flex-wrap:wrap">
      <p style="margin:0;font-size:.9rem">
        This site uses cookies for analytics and functionality.
        See the <a href="/privacy-policy/" style="color:#90caf9">privacy policy</a> for details.
      </p>
      <div style="gap:.75rem;flex-shrink:0">
        <button id="wrt-accept-necessary" style="background:transparent;border:1px solid #fff;color:#fff;padding:.5rem 1rem;cursor:pointer;border-radius:4px;font-size:.85rem">Necessary only</button>
        <button id="wrt-accept-all" style="background:#fff;border:none;color:#1a1a1a;padding:.5rem 1rem;cursor:pointer;border-radius:4px;font-size:.85rem;font-weight:600">Accept all</button>
      </div>
    </div>
    <?php
}

Why Inline Styles Instead of a Stylesheet

The banner fires on page load before the theme’s main CSS is guaranteed to be parsed. An external stylesheet introduces a render dependency; inline styles do not. For a single banner element with a handful of properties, the tradeoff is worth it.

GDPR Compliance Specifics

A technically correct implementation still fails compliance if the UX patterns are deceptive. Regulators in France (CNIL) and Germany (DSK) have published enforcement decisions targeting “dark patterns” in consent flows. The specific violations to avoid are pre-ticked checkboxes, making the “decline” option harder to find than “accept”, and requiring more clicks to withdraw consent than to give it.

Having a proper cookie policy page linked from the banner is not optional – it is part of the transparency requirement. The policy must list every cookie by name, purpose, and retention period. The full list of WordPress core cookies is a useful reference when compiling that document.

For stores, European compliance requirements increasingly overlap between accessibility, privacy, and ecommerce law – auditing one area often surfaces issues in the others.

Users must be able to change their mind. Add a small “Cookie settings” link in the footer that clears the consent cookie and reloads the page, re-triggering the banner.

// Add to footer template or via wp_footer hook
function wrt_cookie_settings_link() {
    echo '<a href="#" style="font-size:.8rem">Cookie settings</a>';
}

This one-liner does not need a dedicated settings page for most sites. The user clicks, the cookie is expired immediately, and the banner reappears on reload. For sites with more complex consent tiers (granular analytics vs. A/B testing vs. personalization), a modal with checkboxes per category is the appropriate next step – but that is a separate project.

Caching Compatibility

Page caching breaks consent-gated logic if not handled correctly. When a full-page cache serves the HTML, PHP never runs, meaning the $_COOKIE check in wp_enqueue_scripts never fires. The banner PHP check also gets bypassed.

Two reliable approaches exist. The first is to exclude pages from cache for users who have not consented – most caching plugins support cookie-based exclusions. In WP Rocket, adding wrt_consent to the “Never Cache” cookies list solves it. The second approach is to move all consent logic to JavaScript and treat PHP enqueuing as a progressive enhancement only.

For high-traffic sites where cache exclusions would reduce hit rates significantly, a JavaScript-only banner with a cookie-gated display handles this more gracefully – the cached HTML includes the banner markup, JS decides whether to show it.

Plugins like Cookiebot or OneTrust are worth the cost for large stores or enterprises that need automated cookie scanning, multi-language support, and IAB TCF compliance for programmatic advertising. For a standard WordPress blog or a small WooCommerce store that uses GA4 and Meta Pixel, the custom implementation above covers every legal requirement at zero ongoing cost and with measurably less JavaScript on the page – typically 0 KB added to the page weight versus 40-120 KB for a CMP SDK.

Често задавани въпроси

  1. Do I need a cookie consent banner on my WordPress site?

    If your site sets non-essential cookies (analytics, marketing, personalization) and receives visitors from the EU, UK, or other jurisdictions with consent laws, yes. WordPress and WooCommerce session cookies are exempt as they are strictly necessary.

  2. Can I build a cookie consent banner in WordPress without a plugin?

    Yes. A PHP hook on wp_footer outputs the banner HTML, a wp_enqueue_scripts hook gates analytics scripts behind the consent cookie, and vanilla JavaScript handles writing the cookie and dismissing the banner.

  3. How do I make my cookie consent banner GDPR compliant?

    The banner must offer a genuine way to decline (not just accept), must not pre-select consent, and must link to a cookie policy. Consent must be recorded with a timestamp, and users must be able to withdraw it as easily as they gave it.

  4. Does a cookie consent banner break page caching in WordPress?

    It can. The safest fix is to add the consent cookie name to your caching plugin’s cookie exclusion list, so the server runs PHP for unconsented users. Alternatively, handle all banner logic in client-side JavaScript so caching is unaffected.

  5. What cookies are set by WordPress and WooCommerce by default?

    WordPress sets session and login cookies; WooCommerce adds cart, session, and checkout cookies. These are all necessary cookies and do not require consent. Third-party scripts like Google Analytics are what trigger the consent requirement.




Related Articles

If you enjoyed reading this, then please explore our other articles below:

More Articles

If you enjoyed reading this, then please explore our other articles below: