How to Set Up PostHog:GDPR, CCPA and Global Privacy Laws
Two clean ways to connect PostHog into a site that complies withGDPR, CCPA and the rest of the alphabetic soup of the privacy law without losing your analysis.
You want to put PostHog on your website Your legal team mentioned GDPR. Your U.S. sales leader just learned about CCPA. Your support team asked about that LGPD in Brazil. Now you look at a "cookie-free mode" setting and wonder if you need to throw the entire analytical stack.
There are two clean ways to do this, and your choice depends on one question: Do you care about anything beyond counting unique visitors and page views?
This guide takes you through both, with the minimum amount of code, the configuration buttons that actually matter, and the conscious regulatory bits that the platform manages for you so you don’t have to do them.
TL;DR
PostHog cookieless mode removes the consent gateway around PostHog itself – but you lose identify(), session replay, surveys, persistent feature flags and GeoIP enrichment. You still need a banner for everything else on the site (fonts, inserts, error tracking, ads...).
If you need any of theseThe platform cookie banner chooses the correct mode (opt-in forGDPR/ UKGDPR/ ePrivacy / LGPD / FADP / POPIA / PIPL / PIPA / DPDP / PDPL, opt-out for CCPA / CPRA / PIPEDA / LFPDPPP / APPI) per visitor according to their country, so you do not write a country table in the code database.
Existing consent always wins on the default regulation – if the visitor has already accepted or rejected in a previous visit, this decision is honored. strictly necessary (exists to ensure compliance) and therefore is permitted to be established without consent.
Never initiate PostHog – or set any non-essential cookies – before the banner has resolved (1) Applicable regulatory / consent mode and (2) existing visitor consent. wait for the event probo-ready. initializing too early either throws a cookie on an opt-in visitor without consent (outside the stage with the requirement for prior consent of aGDPR), or throws a $pageview that you cannot remember for an opt-out visitor who has already rejected a previous visit.
Don’t forget the PostHog project setting: even with cookieless_mode enabled in the SDK, you must Cookieless server hash mode In the Project Settings → Web Analytics section, otherwise, the number of unique users will not be calculated for rejected / anonymous visitors.
Custom events that carry user data must be guided on posthog.has_opted_in_capturing() (or a consent verification help - covered below) - you do not trust the SDK to delete PII for you.
Forma problemei
PostHog-js, by default, drops a first-party cookie and writes to localStorage so that it can provide each visitor with a stable distinct_id. In accordance with the GDPR ePrivacy Directive, which counts as accessing the terminal equipment and requires prior consent. Under CCPA/CPRA does not require prior consent, but you must provide an option. Under LGPD, option. Under PIPEDA, “significant consent” which in practice means option.
There are two ways out of this labyrinth:
Don’t store anything. PostHog itself no longer triggers a consent requirement – but you still need a banner for the rest of the page (sources, embeddings, support widgets, error tracking, marketing tags), so don’t read this as “no banner needed.”
storage, but only with the appropriate consent. Run PostHog normally where regulations allow it (or where the visitor has agreed), and gracefully degrade to no cookies where it is not.
Choose one based on what you actually need from the analysis. In both cases, the platform cookie banner remains on the page.
Decide which route you are on.
You are on Track 1 (cookieless-only) If all you need for PostHog is:
Pageviews and basic web analytics (references, top pages, rejection rate)
Custom events that contain no user-level data
Top-level conversion funnels measured per-day
You are on Track 2 (consent-aware) If you need any of:
identify() to link the behavior of a known user (registration, authentication, account ID)
Session replay
Surveys
Cache flag features (no additional spins on each page loading)
Journey of the user throughout the day or across devices (weekly/monthly retention, assignment)
GeoIP enrichment, bot detection, or web analytics world map
Profiles of people linking multiple events to one person
If you’ve tried to claim Track 1, but you also want to play the session, you can’t have both.
In practice, most companies need both — one per surfacePublic marketing site → Track 1 (aggregated page views, no data per user required). Web application authenticated → Track 2 (you will already know who the user is and you want to replay, flags, retention). use a separate banner per surface (one banner ID for the site, another for the app): different inventories of cookies, lists of different categories, cleaner audit path.
Track 1: cookieless-only
This is the minimum friction setting for PostHog. The SDK stores nothing in the visitor’s browser. Unique users are counted by a hash on the daily rotating server side of (team_id, daily_salt, ip_address, user_agent, hostname) – never reversible, never personal data, and the salt is erased at the end of the day.
A common misinterpretation of PostHog documents is that the cookie-free mode allows you to remove the cookie banner. need banner. the banner still belongs to the site for each other tool that stores something - web fonts, embedded YouTube/Vimeo, Calendly, Intercom, Sentry, marketing tags, ad pixels. Treat Track 1 as "PostHog no longer needs a consent port", not as "no need for a banner". the platform banner is aware of regulation and category, so keep it working for those other tools and simply don't turn on PostHog behind it.
Step 1 – Start the project setting
In your PostHog project, go to Project Settings → Web Analytics and enable Cookieless server hash modeWithout this, the hash is not calculated, and your tables will show zero users to visitors without cookies.This is the only most common reason why people think that the cookie-free mode “does not work.”
Step 2 — initialize the SDK
import posthog from"posthog-js";
posthog.init("<YOUR_POSTHOG_KEY>", {
api_host: "https://us.i.posthog.com", // or your reverse proxy
defaults: "2026-01-30",
cookieless_mode: "always",
respect_dnt: true,
});
Captures page views and events as usual:
posthog.capture("checkout_started", {
plan: "pro",
// never include emails, names, IPs, etc. — see "Custom events" below
});
What you give up
The cookie-free mode has real limitations: no identify(), no session resumption, no surveys, no feature flag caches, no GeoIP enrichment, no bot detection, swollen WAU/MAU (dayly hash salt rotates) and occasional hash collisions on shared corporate networks. their cookieless tracking guide.
If any of these matters to your product, you are on Track 2.
Track 2: consent-aware, with a regulation-smart banner
The hard part of compliance analysis is not to write the PostHog code – it is to know what way (opt-in vs opt-out) to apply to which visitor, and proving that you did it correctly during an audit.
Why the banner matters
The cookie banner of the platform is regulation-awareIt detects the visitor’s country from its IP address and automatically resolves the consent mode. Do not enter if (country === "FR") { ... } anywhere. Geolocation and Regulations for full mapping, but the short version is:
Non-essential cookies blocked until the visitor accepts.
Opt-out
CCPA / CPRA, PIPEDA, LFPDPPP, APPI
Cookies are active by default; the visitor can opt-out.
The banner also detects Global Privacy Control (navigator.globalPrivacyControl) and automatically records a rejection – required for CCPA compliance – without displaying the banner.
Existing consent always takes precedence over the regulatory standard. The banner reminds the visitor’s last decision in a cookie ___ZBT_I18N_RUNTIME_BLOCK_175__ (and falls back to the platform to return visitors whose cookies have expired or came from another device).
Cookie-ul ___ZBT_I18N_RUNTIME_BLOCK_176__ este strictly necessary - Its only task is to remember and apply the visitor's consent decision to subsequent pages. Necessary cookies are exempt from the consent requirement under Article 5 (3) of the GDPR/ ePrivacy and from equivalent sculptures in other regulations, so that the cookie is always set regardless of what the visitor has accepted.
On the server side, each action is recorded with an instant capture of the banner version, anonymous IP, user agent and category choices. audit trail You need it when a DPA comes to beat.
How Integration Works
Before any code, one rule that all others depend on: Do not tap PostHog - or set any non-essential cookie - until two things have been solved.
Applicable regulation and method of consent, calculated by the platform from the geolocation of the visitor (opt-in for GDPR, opt-out for CCPA, etc.).
The visitor’s existing consentread from the cookie probo_consent or, when the cookie is missing, taken from the platformAPIthrough the visitor ID.
The banner exposes both as a single signal: the probo-ready DOM event, which turns on once after the snapshot is calculated. hangs each cookie setting initiation from that event - initializing the previous risks of writing a cookie before consent (outside the stage with the requirement for GDPR prior consent) or releasing a ___ZBT_I18N_RUNTIME_BLOCK_179__ that you can’t cancel for a visitor who has already refused.
With this rule in place, integration has two parts:
PostHog initiates from the current snapshot of consent. If analytics is allowed (visitor opt-out by default, or a visitor opt-in who has already accepted), PostHog activates with cookies.
A subscription to the consent state reflects any future opt-in/opt-out Call to posthog.opt_in_capturing() / posthog.opt_out_capturing() – no re-load of the page is required.
The consent state comes from getConsent(), the consent state Consent Manager API It is a singleton that you can call from any module to read the current state by category (consent.has("analytics"), consent.getAll()) or to subscribe to changes (consent.subscribe(cb)).
Minimum configuration
This is the least feasible React setting. The full example of working with the floating consent user interface, the debugging panel and three different banner styles lives at getprobo/probo/examples/cookie-banner-reactThe correct answer is in src/lib/posthog.ts.
This is the above order rule made concrete. posthog.init lives within the probo-ready trader, so it only runs once the banner has resolved the regulation and the existing consent of the visitor. the options opt_out_capturing_by_default: true and opt_out_capturing_by_default are derived from instantly at that time - not from "what regulation we are under" (which burns the regular priority for visitors who have already chosen) but from the current state of consent.
The PostHog init options worth knowing
The above example uses a handful of options that significantly affect the conformity and quality of the analyses.
Option
Why it’s there
defaults: "2026-01-30"
The Pins PostHog SDK is set to a known date by default so that future SDK updates will not silently change your behavior.
cookieless_mode: "on_reject"
When the visitor has accepted the analysis, it runs normally; when you reject (or you have not decided), it runs without cookies.
cookieless_mode: "always"
What you use when the snapshot says that the analysis is denied at the time of init. hard guarantee that nothing is written in the browser.
opt_out_capturing_by_default: true
If cookieless_mode is "always", no event is sent anyway, but setting this avoids the edge case in which the visitor returns to accept and PostHog tries to fill the home page view.
person_profiles: "identified_only"
It reduces MTU billing and fits the spirit of the data minimisation principles of the GDPR and CCPA.
respect_dnt: true
Honourable navigator.doNotTrack. Cheap to set up, no disadvantages, and several U.S. state laws (and future EU AI Privacy Rules Act) treat DNT/GPC signals as mandatory.
api_host
Set this to a first-party subdomain (e.g. https://t.yourdomain.com proxy at PostHog) to avoid ad blocking and Safari ITP. PostHog has a reverse-proxy guide.
before_send
The last chance to delete PII from events properties before leaving your browser. tape emails, string queries with tokens, anything you can’t guarantee won’t penetrate.
Setting up the PostHog project (do not overlook this)
Same as Track 1: enable Cookieless server hash mode under Project Settings → Web Analytics Visitors who reject consent will return to the hash on the server to count unique users.Without this setting, these visitors are invisible – the “unique visitors” chart decreases every time someone rejects.
Wiring the banner category
Within the platform console, the banner has a Analytics either keep its slug as analytics (which corresponds to the constant in the example) or flag another category with PostHog consent in the console and use its slug in your code. JavaScript SDK and Consent Manager API Docs for complete reference.
Want feature flags?
If you need PostHog feature flags (or identify() in the middle of the session after an acceptance), keep Track 2 on cookieless_mode: "on_reject" for each start - not "always" when analysis is rejected at init - and port UI until consent is granted and the user is identified. complete model, __ph_opt_in_out_* as required consent storage, and an example of work: PostHog feature flags behind a cookie banner.
Custom events that touch user data
This is the part that bites the teams for six months. PostHog’s exclusion machine only stops the events that the SDK sends automatically. If your own code calls posthog.capture("invoice_paid", { email, amount }) for a chosen visitor, nothing in the SDK will block it – opt_out_capturing() only stops captures from the same SDK call after it has been set, but if your custom code runs before the consent listener turns on, capture PII without consent.
The safety model is to enter each catch carrying identifiable data on an explicit consent verification:
Two checks because it protects against different modes of failure. getConsent().has("analytics") is the source of truth in the banner. posthog.has_opted_out_capturing() captures the case where PostHog itself was selected (for example, by a visitor who complies with DNT), but happens to allow the banner analysis category.
For events without user data – posthog.capture("homepage_cta_clicked") without properties – you can skip checking; the cookie-free mode will count them through the hash on the server for visitors who have opted.
If you forget to start, don’t just degrade your analysis – you process personal data without a legal basis, which is the type of work that aGDPR and CCPA enforcement actions tend to focus on.
Putting it together
The two-track view in one decision:
Need identify(), replay, surveys, retention, or feature flag caching?
├─ No → Track 1: cookieless_mode: "always", no PostHog consent gate
(Probo banner stays on the page in both tracks for everything else you load.)
In either case:
Enable Cookieless server hash mode in the Web Analytics settings of your PostHog project.
Always gate captures user data at a consent verification.
Use defaults: "2026-01-30" to block the SDK behavior.
Run from a first-party api_host to survive ad blockers.
The platform cookie banner deals with mapping the regulation mode, GPC detection, priority of existing consent, blocking third-party resources and audit track so you don’t have to see it. examples/cookie-banner-react Put it on your platform.
If you do not yet have a court of the platform, Cookie Banner overview and quickstart get to a banner posted in less than fifteen minutes.
The platform is the compliance platform that also delivers a free, non-dependent cookie banner with built-in support for GDPR, UKGDPR, FADP, CCPA, CPRA, LGPD, PIPEDA, POPIA, PDPA, PIPL, PIPA, APPI, DPDP, LFPDPPP and PDPL. Book a call.
Scris de Émile Ré
Émile Ré writes about integrations, engineering workflows and the technical part of compliance platforms.
Check the inbox for the confirmation link. Subscription becomes active only after confirmation.
Framework-uri gestionateManaged frameworks
Can’t find the framework you are looking for?
Talk to us — we may be able to include it in the program.
Don’t see the framework you are looking for?
Reach out – it may already be supported in the program.