-
Notifications
You must be signed in to change notification settings - Fork 989
Count non-consenting visitors with PostHog cookieless mode #8146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
86b93d7
feat(analytics): count non-consenting visitors with PostHog cookieles…
ankur-arch b4a9c6c
Merge branch 'main' into analytics/posthog-cookieless-before-consent
ankur-arch e5637fc
Merge branch 'main' into analytics/posthog-cookieless-before-consent
ankur-arch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,41 @@ | ||
| import posthog from "posthog-js"; | ||
| import { hasAnalyticsConsent, onAnalyticsConsentChange } from "@prisma-docs/ui/lib/consent"; | ||
|
|
||
| const SUPER_PROPERTIES = { | ||
| site_name: "mono-blog", | ||
| environment: "production", | ||
| }; | ||
|
|
||
| posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, { | ||
| api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, | ||
| capture_pageview: "history_change", | ||
| defaults: "2025-11-30", | ||
| // GDPR/ePrivacy: do not set cookies or capture anything until the visitor | ||
| // grants analytics consent via CookieYes. Opt-in is handled below. | ||
| // GDPR/ePrivacy: no cookies, storage, or persistent identifiers until the | ||
| // visitor grants analytics consent via CookieYes. Opt-in is handled below. | ||
| // Until then (banner ignored or analytics rejected) visitors are counted | ||
| // cookielessly: events carry the $posthog_cookieless sentinel and PostHog's | ||
| // servers derive a daily rotating hash; nothing identifying is stored | ||
| // on-device. Requires "Cookieless server hash mode" in project settings, | ||
| // otherwise these events are dropped at ingestion. | ||
| cookieless_mode: "on_reject", | ||
| // With cookieless_mode this also makes not-yet-decided visitors count as | ||
| // rejected (cookieless) rather than uncaptured. | ||
| opt_out_capturing_by_default: true, | ||
| loaded: (posthog) => { | ||
| posthog.register({ | ||
| site_name: "mono-blog", | ||
| environment: "production", | ||
| }); | ||
| posthog.register(SUPER_PROPERTIES); | ||
| // Returning visitor whose stored consent is already available at init. | ||
| if (hasAnalyticsConsent()) posthog.opt_in_capturing(); | ||
| }, | ||
| }); | ||
|
|
||
| // React to live banner interactions and to CookieYes restoring stored consent. | ||
| onAnalyticsConsentChange((granted) => { | ||
| if (granted) posthog.opt_in_capturing(); | ||
| else posthog.opt_out_capturing(); | ||
| // "pending" must NOT opt out: an explicit opt-out writes an opt-out flag to | ||
| // device storage, and the visitor has not made a decision yet; cookieless | ||
| // capture already covers them. | ||
| onAnalyticsConsentChange((status) => { | ||
| if (status === "granted") posthog.opt_in_capturing(); | ||
| else if (status === "denied") posthog.opt_out_capturing(); | ||
| // Both transitions reset the SDK state that held the registered | ||
| // super-properties, so re-register or later events lose site_name. | ||
| if (status !== "pending") posthog.register(SUPER_PROPERTIES); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,41 @@ | ||
| import posthog from "posthog-js"; | ||
| import { hasAnalyticsConsent, onAnalyticsConsentChange } from "@prisma-docs/ui/lib/consent"; | ||
|
|
||
| const SUPER_PROPERTIES = { | ||
| site_name: "mono-site", | ||
| environment: "production", | ||
| }; | ||
|
|
||
| posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, { | ||
| api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, | ||
| capture_pageview: "history_change", | ||
| defaults: "2025-11-30", | ||
| // GDPR/ePrivacy: do not set cookies or capture anything until the visitor | ||
| // grants analytics consent via CookieYes. Opt-in is handled below. | ||
| // GDPR/ePrivacy: no cookies, storage, or persistent identifiers until the | ||
| // visitor grants analytics consent via CookieYes. Opt-in is handled below. | ||
| // Until then (banner ignored or analytics rejected) visitors are counted | ||
| // cookielessly: events carry the $posthog_cookieless sentinel and PostHog's | ||
| // servers derive a daily rotating hash; nothing identifying is stored | ||
| // on-device. Requires "Cookieless server hash mode" in project settings, | ||
| // otherwise these events are dropped at ingestion. | ||
| cookieless_mode: "on_reject", | ||
| // With cookieless_mode this also makes not-yet-decided visitors count as | ||
| // rejected (cookieless) rather than uncaptured. | ||
| opt_out_capturing_by_default: true, | ||
| loaded: (posthog) => { | ||
| posthog.register({ | ||
| site_name: "mono-site", | ||
| environment: "production", | ||
| }); | ||
| posthog.register(SUPER_PROPERTIES); | ||
| // Returning visitor whose stored consent is already available at init. | ||
| if (hasAnalyticsConsent()) posthog.opt_in_capturing(); | ||
| }, | ||
| }); | ||
|
|
||
| // React to live banner interactions and to CookieYes restoring stored consent. | ||
| onAnalyticsConsentChange((granted) => { | ||
| if (granted) posthog.opt_in_capturing(); | ||
| else posthog.opt_out_capturing(); | ||
| // "pending" must NOT opt out: an explicit opt-out writes an opt-out flag to | ||
| // device storage, and the visitor has not made a decision yet; cookieless | ||
| // capture already covers them. | ||
| onAnalyticsConsentChange((status) => { | ||
| if (status === "granted") posthog.opt_in_capturing(); | ||
| else if (status === "denied") posthog.opt_out_capturing(); | ||
| // Both transitions reset the SDK state that held the registered | ||
| // super-properties, so re-register or later events lose site_name. | ||
| if (status !== "pending") posthog.register(SUPER_PROPERTIES); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: prisma/web
Length of output: 7191
🏁 Script executed:
Repository: prisma/web
Length of output: 25171
🌐 Web query:
PostHog JavaScript SDK opt_in_capturing register super properties reset behavior source💡 Result:
In the PostHog JavaScript SDK, the interaction between reset behavior, consent, and super properties is critical for accurate data tracking [1][2]. Reset and Consent Behavior When you call posthog.reset, the SDK clears the user's identity (setting a new random distinct_id), removes cached data, and clears the consent state [1][2]. Crucially, because reset clears consent, it returns the instance to the default consent state [1][2]. If you have configured the SDK with opt_out_capturing_by_default: true, the default state is "opted out" [1][2]. Consequently, if you call posthog.reset after posthog.opt_in_capturing, the SDK will silently stop capturing data [1][2]. The recommended pattern is to always call posthog.reset before calling posthog.opt_in_capturing [1][2]. Super Properties Behavior Super properties are properties set using posthog.register that are automatically included with every event [3][2]. - Persistence: Super properties are stored in persistent storage (e.g., cookies or local storage) [3][2]. - Reset Impact: Calling posthog.reset clears these stored super properties, as it clears the instance's cached data and persistence [1][2]. - Updates: If you register the same property multiple times, the new value overwrites the old one [3][2]. To remove a specific super property without a full reset, use posthog.unregister('property_name') [3]. Summary of Best Practices To maintain proper state and consent, follow this sequence: 1. Initialize with appropriate defaults (e.g., opt_out_capturing_by_default: true) [4][5]. 2. Call posthog.reset to clear previous identity and super properties [1][2][6]. 3. Call posthog.opt_in_capturing to establish consent [1][2][4]. 4. Call posthog.register to set your desired super properties for the new session [3][2].
Citations:
🏁 Script executed:
Repository: prisma/web
Length of output: 857
🌐 Web query:
github PostHog posthog-js v1.415.7 opt_in_capturing implementation💡 Result:
In the PostHog JavaScript SDK (posthog-js), the opt_in_capturing method is used to enable data capturing and persistence (cookies/localStorage) for the current PostHog instance [1][2]. This is typically used in conjunction with a consent management platform (CMP) or a manual consent banner to comply with privacy regulations [3]. Key Implementation Details: 1. Recommended Pattern: The standard approach is to initialize PostHog with opt_out_capturing_by_default: true in your config [3][4]. This ensures no data is captured until the user provides explicit consent [3]. When the user grants consent, you then call posthog.opt_in_capturing [3]. 2. Method Signature: The method accepts an optional configuration object to customize the opt-in behavior [1][2]: posthog.opt_in_capturing(options?: { captureEventName?: EventName | null | false; captureProperties?: Properties; }) 3. Customization Options: - captureEventName: By default, calling this method captures an $opt_in event [1][2]. You can set this to a custom string to use a different event name, or set it to false if you wish to opt the user in without sending an event [1][2]. - captureProperties: You can provide a dictionary of properties to be attached to the opt-in event [1][2]. 4. Persistence: Calling opt_in_capturing automatically enables persistence (re-enabling cookies/localStorage) [1][5]. The SDK manages this state based on your instance configuration, such as the opt_out_capturing_persistence_type setting [6]. Example Usage: // Simple opt-in posthog.opt_in_capturing; // Opt-in with custom event and properties posthog.opt_in_capturing({ captureEventName: 'Privacy Accepted', captureProperties: { source: 'banner' } }); // Opt-in without sending an event posthog.opt_in_capturing({ captureEventName: false }); When a user withdraws or denies consent, you should call posthog.opt_out_capturing to stop further data collection and disable persistence [3].
Citations:
🏁 Script executed:
Repository: prisma/web
Length of output: 378
🌐 Web query:
site:github.com/PostHog/posthog-js/blob/v1.415.7/src/posthog-core.ts "opt_in_capturing"💡 Result:
The requested GitHub path appears to be unavailable: tag
v1.415.7orsrc/posthog-core.tsreturns 404 Not Found. ()The
opt_in_capturingAPI is documented as enabling event capture and has corresponding status-check methods such ashas_opted_in_capturing(). (app.unpkg.com)Citations:
🏁 Script executed:
Repository: prisma/web
Length of output: 50367
🏁 Script executed:
Repository: prisma/web
Length of output: 4761
🏁 Script executed:
Repository: prisma/web
Length of output: 17644
🏁 Script executed:
Repository: prisma/web
Length of output: 13642
Register properties after initial opt-in.
When
hasAnalyticsConsent()is true,posthog.opt_in_capturing()resets persistence and removesSUPER_PROPERTIES. Moveposthog.register(SUPER_PROPERTIES)after the opt-in in all three instrumentation files.📍 Affects 3 files
apps/blog/src/instrumentation-client.ts#L25-L28(this comment)apps/docs/src/instrumentation-client.ts#L25-L28apps/site/src/instrumentation-client.ts#L24-L27🤖 Prompt for AI Agents