Platforms

How to add a cookie consent banner to an Astro site

In Astro, put the snippet in the head of your shared layout (src/layouts/Layout.astro) as the first script, with is:inline so Astro renders it exactly as written. Mark Google and Meta tags in the same layout with type="text/plain" and data-cq-category, and keep marked tags in the layout rather than in pages when you use the ClientRouter.
Published 20 October 2026 · By the Cookiequiet team · Markdown

Steps

Open your layoutOpen the layout component every page uses, typically src/layouts/Layout.astro, which holds the html, head and body tags around a slot.
Add the snippet as the first scriptIn the head, before any other script, add the Cookiequiet snippet with the is:inline directive. Astro leaves such a script untouched: no bundling, no type="module".
Mark the tags in the layoutAdd the Google tag or Meta Pixel in the same head with is:inline, type="text/plain" and data-cq-category set to analytics or marketing. Or keep the Google tag loading in Consent Mode with Google's default consent block placed before it.
Handle embedsWrite YouTube or Vimeo iframes with data-cq-src instead of src so they load after consent. Facade components that create the player on click are held by the auto-blocker until the category is granted.
Mind the ClientRouterWith view transitions, scripts in the head run once per full load and marked scripts in page bodies are not activated during client-side navigation. Keep marked tags in the layout and read window.Cookiequiet in island scripts.
Add the Cookie preferences linkIn the footer component, add a plain anchor with href="#cq-preferences" and the data-cq-show-preferences attribute.
Test with Reject allRun astro dev or preview the build, open the site in a private window, click Reject all and check that no _ga or _fbp cookie is set. Accept all, reload, and check the consent log.

Where to paste the snippet in Astro

Astro layouts are components that provide the page shell, with <html>, <head>, <body> and a <slot /> for the page content. The snippet goes in the head of that layout, before any other script. Astro processes a <script> only when its sole attribute is src; ours carries data attributes, so Astro leaves it alone, but is:inline makes the intent explicit:

astro
---
// src/layouts/Layout.astro
const { title } = Astro.props;
---
<html lang="en">
<head>
<meta charset="utf-8" />
<script is:inline src="https://cookiequiet.com/cq.js" data-site="YOUR_SITE_KEY" data-consent-mode="1" data-auto-block="1" defer></script>
<meta name="viewport" content="width=device-width" />
<title>{title}</title>
</head>
<body>
<slot />
</body>
</html>

Every page that uses the layout, including Markdown pages that name it in their layout frontmatter, now loads the banner. If your project has several layouts, put the line in each, or in a small Head.astro component they all include.

How to handle the tags your site loads

Marked tags in the layout. Add the tags after the snippet, with is:inline, type="text/plain" and a category. Cookiequiet runs them when the category is granted, or at once on later visits when a stored choice includes it:

html
<script is:inline type="text/plain" data-cq-category="analytics" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>
<script is:inline type="text/plain" data-cq-category="analytics">
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXX');
</script>

The Meta Pixel base code gets data-cq-category="marketing"; its noscript image must be removed, as the Meta Pixel guide explains.

Consent Mode instead of marking. To keep the Google tag loading with denied defaults and cookieless pings, place Google's default consent block (an inline script) between the snippet and the Google tag, as shown in the Google Analytics 4 guide. Cookiequiet sends the update after each choice.

Partytown. Keep cq.js on the main thread: it needs the document. If you move the Google tag into a Partytown worker, forward dataLayer.push so the consent commands reach it.

Embeds. Write iframes with data-cq-src instead of src. Facade components that build the YouTube player on click create the iframe with JavaScript; the auto-blocker holds youtube.com/embed under marketing until that category is granted. See YouTube embeds and consent.

What changes with the ClientRouter

With view transitions, Astro swaps the page content without a full load. The snippet in the head keeps running across navigations, the stored choice stays applied and the banner does not reappear. Two consequences:

  • Marked scripts inside a page body loaded by client-side navigation are not activated until the next consent event, because Cookiequiet activates marked tags when a choice is applied on load and after each choice. Keep marked tags in the layout.
  • Island scripts that need the choice should read window.Cookiequiet.get() (the c array lists the granted categories) and subscribe with window.Cookiequiet.on(callback), which returns an unsubscribe function.

In src/components/Footer.astro or wherever the footer lives:

html
<a href="#cq-preferences" data-cq-show-preferences>Cookie preferences</a>

How to test

  1. Run astro dev (or astro build then astro preview) and open the site in a private window.
  2. Click Reject all: the cookies are cq_consent and nothing from Google or Meta.
  3. Clear the cookies, click Accept all, reload: the marked scripts run.
  4. With the ClientRouter, navigate between pages and check that the choice holds.
  5. Check the consent log in the dashboard.

What is specific to Astro

  • Static output. The banner is a client script; the built site works on any static host (Netlify, Vercel, Cloudflare Pages, GitHub Pages). The article on cookie banners for static sites covers the rest.
  • Content Security Policy. Allow https://cookiequiet.com in script-src and connect-src.
  • Multiple languages. The banner's language follows the visitor's browser, with every string editable per language; it does not read the page's lang.
  • Content collections and Markdown. Pages rendered through the layout need no change; tags pasted inside Markdown content would bypass the layout, so keep tags in the layout.

Questions

Astro bundles and transforms scripts it processes, adds type="module" and can hoist them. is:inline tells Astro to render the tag exactly as written, which keeps the data attributes, the defer attribute and the position in the head. A script with attributes other than src is not processed anyway, but the directive makes that explicit.
The banner reads its configuration from the attributes of its own script tag (document.currentScript) and must run before the tags it controls. A bundled module import would lose the attributes and the ordering.
No. The snippet in the head persists across client-side navigations and the stored choice stays applied. Only marked scripts inside page bodies loaded by navigation are not activated until the next choice, so keep marked tags in the layout and gate islands through window.Cookiequiet.
An integration that injects a script into the head (injectScript with the head-inline stage) works as long as the result is the same tag in the same position. The layout is simpler to audit.

Put a quiet banner on your website today

Free for one website with every core feature, no card and no trial clock. Upgrade a website to Starter for $3 a month to match your brand with AI and drop the branding, or to Pro for $9 to design it with the AI agent.