Cookie consent
Gate cookie-dropping analytics, chat, and affiliate scripts behind a GDPR consent banner with config.cookieBanner and config.consentPolicy.
The consent banner is configured in @repo/config and rendered client-side. config.cookieBanner governs whether it shows and which scripts wait on it; config.consentPolicy governs which visitors must answer it.
The three banner modes
config.cookieBanner is a required key typed boolean | "auto"; generated projects ship "auto".
| Value | Behavior |
|---|---|
true | Banner always shows; consent-required scripts wait for it. |
false | Banner never shows; every script loads freely, no consent. |
"auto" (shipped) | Banner shows only when a cookie-dropping provider is enabled and the visitor must consent. |
Under "auto" a cookie-dropping third party must be enabled: consent-required analytics (config.analytics.google, posthog), support chat (config.support.crisp, chatwoot), or an affiliate tracker (config.affiliate.refgrow, affonso, promotekit). Privacy-focused analytics (umami, plausible, openpanel, datafast, ahrefs, vercel) drop no cookies and never trigger or wait on the banner.
Out of the box no cookie-dropping provider is enabled, so the banner stays hidden. In development a provider counts only when its enableInDev flag is set (config.analytics, config.support, config.affiliate); in production every enabled provider counts.
Who must consent
config.consentPolicy decides which visitors must answer explicitly; everyone else is granted automatically once geo detection settles, so US visitors never see a banner.
| Value | Behavior |
|---|---|
"gdpr-and-unknown" (default) | GDPR-country visitors and visitors whose country can't be detected must consent - fail-safe. |
"gdpr-only" | Only visitors detected in a GDPR country must consent; undetected visitors are granted automatically. |
"everyone" | Every visitor must consent explicitly; nobody is granted automatically. |
"never" | Consent is implicit for everyone - the banner never shows and all analytics fire instantly (equivalent to cookieBanner: false). |
isGdprCountry (@repo/utils/helpers) fails safe - an unknown location counts as GDPR, so undetected visitors see the banner under the default policy. Set consentPolicy: "gdpr-only" to auto-grant them instead.
The acceptedCookies cookie
The visitor's choice persists in a tri-state acceptedCookies cookie:
| Cookie value | Meaning |
|---|---|
"true" | Consented - cookie-dropping scripts load. |
"false" | Declined - consent-required scripts stay off; support chat and affiliate pixels still load (they only surface the banner). |
| unset | No choice yet - the banner shows when its surfacing conditions hold; consent-required scripts wait. |
- Google Analytics loads only on grant - gtag.js pings nothing and drops no cookie beforehand. Grants carry the full Consent Mode v2 signal set,
ad_user_data/ad_personalizationincluded. - Withdrawing consent also clears the identity held meanwhile (GA
user_id: null, PostHogreset()); the withdrawal survives that clearing and a later sign-out. - Privacy-focused providers keep their identity on withdrawal - it never gated on consent.
- Traits wait for consent: with a banner required, privacy-focused providers get the pseudonymous id at once, email/name and attribution only after consent.
cookieBanner: false (equivalent to consentPolicy: "never") loads every script with no consent gate - only for projects with no cookie-dropping providers or outside consent-law jurisdictions.
Other first-party cookies
| Cookie | Purpose |
|---|---|
first_touch | First-visit attribution (UTM tags, referrer, landing page) - 90 days, no identifier, written independently of the banner. Full attribute table: Pageviews and attribution. |