Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Cookie Guard Bundle Laravel Package

freshheads/cookie-guard-bundle

Symfony bundle from Freshheads that adds cookie consent guarding to your app, helping ensure cookies and related scripts only load when visitors permit. Simple Composer install and configuration for PHP projects.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: this is a Symfony bundle, not a Laravel package. There is no service provider, no config publish tag, and Composer will not give you anything usable in a Laravel app. What you actually get from freshheads/cookie-guard-bundle is a pattern plus two small pieces of code worth porting:

  1. src/ — a Twig extension that wraps template blocks so their contents are emitted as <script type="text/plain" data-cookie-guard="…"> instead of executable script tags.
  2. Resources/public/js/ — a small vanilla-JS runtime that reads the consent cookie and, on acceptance, clones each inert script back into the DOM as a real text/javascript node.

The repo was archived in Nov 2022 and has zero dependents, so treat it as reference source, not a dependency. Clone it, read those two files, then reimplement ~60 lines in Blade + a JS module.

Minimal steps in a Laravel app:

git clone https://github.com/freshheads/FHCookieGuardBundle /tmp/cgb
# read: src/*Extension*.php  and  Resources/public/js/*

Then:

  1. Copy the guard JS into resources/js/cookie-guard.js and import it from app.js.
  2. Add a Blade directive that emits the inert script tag (see below).
  3. Write the consent cookie from your cookie banner (cookie_guard=analytics,media).
  4. Call the activation function on page load and whenever consent changes.

First real use case: a YouTube embed and a Google Analytics snippet that must not fire until the visitor clicks "Accept". Wrap both, verify in DevTools that no request leaves the browser before consent.

// app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\Blade;

Blade::directive('cookieGuard', fn ($group) =>
    "<?php \$__g = $group; ?><script type=\"text/plain\" data-cookie-guard=\"<?= e(\$__g) ?>\">");
Blade::directive('endCookieGuard', fn () => '</script>');
@cookieGuard('analytics')
  ga('create', 'UA-XXXX', 'auto');
@endCookieGuard

Implementation Patterns

Server-render the placeholder, activate client-side. The whole trick is the type="text/plain" attribute: the browser parses the tag but never executes it. The moment consent arrives, you rebuild it. Keep the source-of-truth consent in a cookie so a page reload doesn't flash ungated content.

The activation loop (port of the bundle's runtime):

// resources/js/cookie-guard.js
const COOKIE_NAME = 'cookie_guard';

export function acceptedGroups() {
  const entry = document.cookie.split('; ').find(c => c.startsWith(COOKIE_NAME + '='));
  return entry ? decodeURIComponent(entry.split('=')[1]).split(',') : [];
}

export function activateGroups(groups = acceptedGroups(), scope = document) {
  scope.querySelectorAll('script[type="text/plain"][data-cookie-guard]').forEach(old => {
    if (!groups.includes(old.dataset.cookieGuard)) return;

    const s = document.createElement('script');
    [...old.attributes].forEach(a => {
      if (['type', 'data-cookie-guard'].includes(a.name)) return;
      s.setAttribute(a.name, a.value);
    });
    s.text = old.text;
    s.async = false;            // preserve execution order for `src` scripts
    old.replaceWith(s);
  });
}

Non-script embeds. Iframes and images don't need type juggling — just defer src:

<iframe data-cookie-guard="media"
        data-src="https://www.youtube.com/embed/{{ $video }}"
        title="Video" loading="lazy"></iframe>
document.querySelectorAll('iframe[data-cookie-guard]').forEach(el => {
  if (acceptedGroups().includes(el.dataset.cookieGuard)) el.src = el.dataset.src;
});

Wire it to the banner, not to page load only. Call activateGroups() once on DOMContentLoaded (returning visitors with the cookie already set get their scripts immediately) and again after the user accepts, so nothing requires a reload. Dispatch a custom event from your banner and listen for it:

document.addEventListener('cookie-guard:changed', () => activateGroups());

Group naming mirrors your consent categories, not your vendors: analytics, marketing, media. If your banner only has a single yes/no, one group (all) is enough — the bundle's config in DependencyInjection/Configuration.php shows the cookie name and lifetime as the only knobs, so there's no hidden state.

Testing. Set the cookie in a feature test and assert the markup, and separately assert the absence of an executable tag:

$this->withCookie('cookie_guard', 'analytics')->get('/')
     ->assertSee('data-cookie-guard="analytics"', false)
     ->assertDontSee('<script src="https://www.googletagmanager.com', false);

Note this proves the markup, not the runtime — the actual gating is browser behaviour, so cover the JS with a Dusk/Browser test or a Jest test on activateGroups against a jsdom fixture.

Gotchas and Tips

  • Archived and Symfony-era. Last release 2022, no security maintenance, Twig 2/3 and Symfony 4/5 assumptions in the extension. Do not composer require it into a Laravel app expecting it to work; vendor-copy the logic and attribute it (MIT).
  • CSP will bite you. If you run a nonce-based script-src, the cloned script node loses the nonce (the bundle never handled this). Copy the nonce attribute across when rebuilding, otherwise consent silently fails with a console CSP error and no network request.
  • Scripts injected after load are not guarded. Anything your own JS appends (widgets, document.write, SPA route changes) bypasses the guard entirely. Either route all third-party loading through activateGroups() or add a MutationObserver on head.
  • Ordering. Re-appended <script src> tags default to async, so two guarded scripts that depend on each other may run out of order. Set s.async = false (as above) and keep dependent code inside the same group.
  • document.write inline snippets (older ad/analytics tags) cannot be re-injected after parsing and will corrupt or blank the page. Rewrite them to the modern async form before guarding.
  • Server-side the cookie means nothing. Never use it for authorization or to decide what HTML to send — it's user-editable. Guarding is purely a browser-execution concern; render the placeholders unconditionally.
  • SEO. Guarded embeds and inline JSON-LD are invisible until consent. Keep <meta>, structured data, and above-the-fold content outside the guarded block, and keep a text fallback/link inside the embed placeholder.
  • Cookie hygiene. Set Secure, SameSite=Lax, and a lifetime that matches your consent-renewal policy (commonly re-ask every 6–12 months). Path must be / or returning visitors on other routes lose consent and re-prompt.
  • Debugging checklist, in order: does the element have type="text/plain"? does document.cookie actually contain the group? does the group string match exactly (case, whitespace, URL-encoding)? did the clone get a nonce? anything in the console about CSP or Refused to execute?
  • Extension points are all in your own code: the group → category map, the cookie reader/writer, and the activation trigger. Keep the reader in one module — swapping from a cookie to localStorage or an off-the-shelf consent manager should be a one-file change.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package