alt-design/alt-cookies
Alt Cookies is a lightweight Laravel package for cookie consent management. Publish config and Blade views, sort cookies into categories, and conditionally load scripts so your site stays GDPR-friendly without heavy JavaScript dependencies.
Confirm the basics first. This is a Statamic addon (not a plain Laravel package), so treat it as a Statamic extension: it registers a service provider, likely a tag/partial for the banner, a config file, and some front-end JS. With only 4 stars and no dependents, budget 20 minutes to read src/, resources/views/, config/, and routes/ before wiring anything into a client site — the README is likely thin compared to the code.
Decide which line you're on before touching Composer. As of v1.3.2-alpha there are two lines: stable 1.2.x and the 1.3 pre-release. Composer will not install an alpha unless you ask for it, so a plain composer require keeps you on 1.2. The 1.3 line is where the cookie scan, no-reload consent application, and updateConsent() live — pin it explicitly if you want them:
composer require alt-design/alt-cookies:^1.3@alpha # or an exact 1.3.2-alpha pin
composer show alt-design/alt-cookies # confirm version + autoload path
Minimal path:
Install and verify discovery
composer show alt-design/alt-cookies # confirm version + autoload path
Statamic auto-discovers addons; check the CP's Addons list or php please support:details if the provider doesn't appear.
Publish config (and views if you'll restyle)
php artisan vendor:publish --tag=alt-cookies-config
php artisan vendor:publish --tag=alt-cookies-views
If those tags don't exist, run php artisan vendor:publish interactively and pick the provider — confirm the exact provider namespace from composer.json's extra.statamic.providers.
Set the GA measurement ID in the published config or the addon's CP settings screen (whichever it exposes). Prefer an env var (ALT_COOKIES_GA_ID) so staging doesn't pollute production analytics. On 1.3 also set the default consent state here rather than in JS — the release fixes a double-load of that default, so it should now be a single source of truth.
Run the cookie scan on staging before writing any banner copy. On 1.3 the Alt Cookies settings page (and the dashboard) gains a scan tab that crawls the site's pages in a headless browser to catch JS-set cookies, not just Set-Cookie headers. Do this first: it tells you which cookies exist and therefore how many categories you actually need. Generate the policy copy from the report, then edit it — it is a formatted draft for Bard, not a legal document.
Render the banner once, in your base Antlers layout, immediately before </body>. The addon will expose either a tag (e.g. {{ alt_cookies }} / {{ alt_cookies:banner }}) or a partial — check src/Tags/ for the registered handle rather than guessing.
First use case: gate GA4 behind consent on a marketing site, with a "manage preferences" entry point. Load the page in incognito, confirm no gtag/js request until you accept, then accept and confirm the script fires without a reload (1.3 behaviour; 1.2 reloads). Check the consent cookie in DevTools → Application → Cookies, then follow the generated policy's preferences link and confirm it reopens the banner with the current choice preselected.
If the site is Blade-first rather than Antlers, check whether the addon ships a Blade component/directive; otherwise include the tag via {{ Statamic::tag('alt_cookies') }} or render the published partial directly.
Banner inclusion is a layout concern, not a page concern. Put it in resources/views/layout.antlers.html (or your Blade base layout). Duplicating it into individual templates is the fastest way to get double banners and double consent writes.
Let the scan drive your category model. The 1.3 scanner report is the cheapest source of truth for "what are we actually setting" — server and JS cookies in one list. Convert that list into categories in config, then map scripts to categories. Re-run the scan after adding any third-party integration; it's a far better regression check than eyeballing Network tab.
Use the no-reload apply, but know what it does and doesn't cover. 1.3 applies accept/reject without a page reload. That's fine for the addon's own managed scripts. Any script you inject yourself (custom pixels, embeds, chat widgets) won't automatically re-run — call the new updateConsent() after your own state changes so the addon stays the authority. Since updateConsent() is new in 1.3, feature-detect it (typeof updateConsent === 'function') if you maintain a theme that spans both lines.
Don't hand-roll gtag in the layout. The whole value of the package is that the GA snippet is emitted conditionally. If you paste the standard Google snippet into your <head>, you've defeated the addon and created a GDPR problem. If you need GTM instead of raw GA, wire it through the addon's script config, or use Consent Mode: default everything to denied inline, then push consent.update from the addon's accept handler.
Ship a persistent preferences entry point. The generated policy copy includes a link hook for reopening the banner — wire that hook into the actual banner (usually a JS call or a data- attribute on the link). Put "Cookie preferences" in the footer of every page; regulators and clients both expect consent to be as easy to withdraw as to give.
Static caching changes the design. If you use Statamic's static cache or a full-page CDN cache, never render consent state server-side. The banner markup must be cacheable and the accept/decline logic must be client-side. The 1.3 no-reload behaviour helps here — no reload means no chance of a cached page serving stale consent markup — but it does not remove the requirement that the default state be client-side.
Multi-environment and multisite. Never run the cookie scan against production: it crawls pages, warms caches, and can trip rate limits, form submissions, or outbound API quotas. Scan staging behind the same basic auth you use for the client. On multisite, prefer one shared policy with per-site banner copy via translations (lang/en/alt-cookies.php style) rather than per-site config drift.
Testing workflow for day-to-day work:
collect / gtag to prove nothing fires pre-consent.expires, SameSite, and Secure attributes match your policy wording.1.3.2-alpha) in composer.json rather than ^1.3@alpha if the client site is live — a later 1.3.x-alpha can land under you otherwise.1.2.x → 1.3.2-alpha and 1.3.1 → 1.3.2 are both additive. The risk is entirely in your code: updateConsent() doesn't exist on 1.2, so guard it.php artisan config:clear. In production, config:cache will freeze the GA ID, cookie name, and default consent state — which is usually what you want, but it makes "why didn't my change deploy" a five-minute mystery.resources/views/vendor/{namespace}/. Clear the view cache (php artisan view:clear) after overriding, and remember Statamic's Antlers runtime cache if the markup still looks stale. Your published banner view is the thing most likely to break the "reopen preferences" link hook after an upgrade — check it whenever you bump.cookie_consent cookie from a previous agency, you'll get two competing banners. Behind a load balancer, ensure TrustProxies is configured or Secure/SameSite flags will silently misbehave.public/. The 1.3 no-reload apply makes this more visible — previously a reload could mask a blocked inline script.cp and preview routes, or conditionally render on Statamic::isCp() / the preview session. Also confirm the scanner tab itself isn't crawling CP URLs or triggering the banner recursively.ad_user_data, ad_personalization) — a simple accept/decline banner from a 4-star addon may not emit them. This is the most likely place you'll need to extend rather than configure.updateConsent() for scripts you load yourself → forking the tag. Avoid patching vendor/.document.cookie in console) → addon JS enqueued once, not twice? → script emitted but blocked by CSP? → your own scripts applied via updateConsent() or still waiting for a reload? → page served from static cache with stale inline state? → config cached with the wrong ID? → scan report stale, so a cookie is uncategorised and silently skipped? Work that list in order; it resolves most issues in under ten minutes.How can I help you explore Laravel packages today?