Conversation
WalkthroughThis pull request implements server-side PostHog event tracking infrastructure and integrates it across multiple user flows. A new PostHog server client module is created with singleton caching and graceful error handling. Event capture is triggered during organization and user creation, survey operations (creation, publishing, response export), integration connections, team member invitations, trial activations, and authentication events. The survey creation schema is extended to track creation source (blank vs. template). Response count caching is added for event batching during survey response ingestion. The 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/app/setup/organization/create/actions.ts`:
- Around line 53-57: The capturePostHogEvent call in create/actions.ts is
sending ctx.user.id twice (as distinctId and as user_id in properties); remove
the redundant user_id property from the event payload to avoid duplicating PII.
Locate the call to capturePostHogEvent(ctx.user.id, "organization_created",
{...}) and delete the user_id field from the properties object (leaving
organization_id and is_first_org intact) so only distinctId carries the user
identifier.
In `@apps/web/lib/posthog/server.ts`:
- Around line 25-27: The code only caches posthogServerClient in
globalForPostHog during development and re-registers signal handlers on every
cold start; to avoid duplicate registrations, add a guard flag (e.g.,
globalForPostHog.posthogHandlersRegistered) and check it before attaching the
handlers that call shutdown(), set the flag after registration, and ensure
shutdown() remains idempotent; reference the existing globalForPostHog,
posthogServerClient, and shutdown() symbols when implementing the guard so the
handlers are registered only once per runtime.
In `@apps/web/modules/auth/lib/authOptions.ts`:
- Around line 366-368: The sign-in flow awaits captureSignIn which performs
extra Prisma queries and adds latency; change the three call sites (the
captureSignIn invocation at the shown return block and the occurrences around
lines 378-379 and 383-385) to fire-and-forget by removing the await and invoking
captureSignIn(...).catch(err => processLogger?.error(...)) (or similar
non-throwing error handling) so analytics run asynchronously without delaying
the auth response; keep updateUserLastLoginAt awaited if its write is required
synchronously.
- Around line 343-358: Wrap the enrichment logic in captureSignIn in a try/catch
so any Prisma failures do not bubble up and break sign-in; call
prisma.membership.count and prisma.user.findUnique inside the try, compute
isFirstLoginToday using a UTC-stable comparison (e.g., compare
userData.lastLoginAt.toISOString().slice(0,10) to new
Date().toISOString().slice(0,10)), then call capturePostHogEvent; on error catch
and log the error (or swallow) but always allow the authentication flow to
continue. Ensure you reference captureSignIn, prisma.membership.count,
prisma.user.findUnique, and capturePostHogEvent when making the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: fba98218-dae5-4667-afb1-cf6cab9b35d5
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (18)
apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/actions.tsapps/web/app/(app)/environments/[environmentId]/workspace/integrations/actions.tsapps/web/app/api/(internal)/pipeline/route.tsapps/web/app/setup/organization/create/actions.tsapps/web/lib/posthog/capture.tsapps/web/lib/posthog/index.tsapps/web/lib/posthog/server.tsapps/web/modules/auth/lib/authOptions.tsapps/web/modules/auth/signup/actions.tsapps/web/modules/ee/billing/actions.tsapps/web/modules/ee/sso/lib/sso-handlers.tsapps/web/modules/organization/settings/teams/actions.tsapps/web/modules/survey/components/template-list/actions.tsapps/web/modules/survey/components/template-list/index.tsxapps/web/modules/survey/editor/actions.tsapps/web/next.config.mjsapps/web/package.jsonpackages/cache/src/cache-keys.ts
…rvey_published action to survey_updated
|




What does this PR do?
Adds 10 custom server-side PostHog events to replace fragile autocapture-based tracking. Previously, Formbricks relied entirely on PostHog autocapture with text matching and URL regex — any button label change or URL restructure silently broke analytics. Signup and signin were not tracked at all.
Changes
Foundation:
posthog-nodeserver-side SDKapps/web/lib/posthog/server.ts) using theglobalThispattern (matching Prisma client), with serverless-safe flushing (flushAt: 1)capturePostHogEvent()helper that never throws and no-ops whenPOSTHOG_KEYis unset (safe for self-hosted)posthog-nodetoserverExternalPackagesinnext.config.mjsNew events (not previously tracked):
user_signed_up— fires on credential signup and SSO signup, withauth_provider,email_domain,signup_source(direct/invite)user_signed_in— fires in NextAuthsignIncallback withauth_provider,organization_count,is_first_login_today(enrichment queries gated onPOSTHOG_KEY)Migrated from autocapture:
survey_published— fires on status transition toinProgress(not on button click)survey_created— fires increateSurveyActionwith newcreatedFromfield ("blank"vs"template")organization_created— fires after org + membership creation, uses pre-computedhasNoOrganizationsforis_first_orgfree_trial_started— fires instartProTrialActionafter Stripe confirmationintegration_connected— fires withintegration_typeandorganization_idteam_member_invited— fires after invite email sent, withinvitee_roleresponses_exported— fires withformat,filter_applied,organization_idsurvey_response_received— sampled (1st response + every 100th), uses Redis-cached response count (60s TTL) viacache.withCache()to avoid DB hits on every responseHow should this be tested?
POSTHOG_KEYenv var and verify events appear in PostHog Live Events:user_signed_upwithauth_provider: "credentials"user_signed_inwithorganization_countandis_first_login_todaysurvey_createdwithcreated_from: "blank"survey_createdwithcreated_from: "template"survey_publishedwithsurvey_type,question_countorganization_createdfree_trial_startedintegration_connectedwithintegration_typeteam_member_invitedwithinvitee_roleresponses_exportedwithformatsurvey_response_receivedfires on 1st and every 100thPOSTHOG_KEYand verify all flows work normally with no errors (graceful no-op)console.logstatements, no client-side bundle pollution (server-onlyimport)Checklist
Required
pnpm buildconsole.logsgit pull origin mainAppreciated