Skip to content

feat: custom posthog events#7647

Merged
pandeymangg merged 12 commits intomainfrom
feat/custom-posthog-events
Apr 9, 2026
Merged

feat: custom posthog events#7647
pandeymangg merged 12 commits intomainfrom
feat/custom-posthog-events

Conversation

@pandeymangg
Copy link
Copy Markdown
Contributor

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:

  • Installed posthog-node server-side SDK
  • Created a PostHog server singleton (apps/web/lib/posthog/server.ts) using the globalThis pattern (matching Prisma client), with serverless-safe flushing (flushAt: 1)
  • Created a fire-and-forget capturePostHogEvent() helper that never throws and no-ops when POSTHOG_KEY is unset (safe for self-hosted)
  • Added posthog-node to serverExternalPackages in next.config.mjs

New events (not previously tracked):

  • user_signed_up — fires on credential signup and SSO signup, with auth_provider, email_domain, signup_source (direct/invite)
  • user_signed_in — fires in NextAuth signIn callback with auth_provider, organization_count, is_first_login_today (enrichment queries gated on POSTHOG_KEY)

Migrated from autocapture:

  • survey_published — fires on status transition to inProgress (not on button click)
  • survey_created — fires in createSurveyAction with new createdFrom field ("blank" vs "template")
  • organization_created — fires after org + membership creation, uses pre-computed hasNoOrganizations for is_first_org
  • free_trial_started — fires in startProTrialAction after Stripe confirmation
  • integration_connected — fires with integration_type and organization_id
  • team_member_invited — fires after invite email sent, with invitee_role
  • responses_exported — fires with format, filter_applied, organization_id
  • survey_response_received — sampled (1st response + every 100th), uses Redis-cached response count (60s TTL) via cache.withCache() to avoid DB hits on every response

How should this be tested?

  • Set POSTHOG_KEY env var and verify events appear in PostHog Live Events:
    • Sign up with email/password → user_signed_up with auth_provider: "credentials"
    • Sign in → user_signed_in with organization_count and is_first_login_today
    • Create a survey from scratch → survey_created with created_from: "blank"
    • Create a survey from a template → survey_created with created_from: "template"
    • Publish a survey → survey_published with survey_type, question_count
    • Create an organization → organization_created
    • Start a free trial (Formbricks Cloud) → free_trial_started
    • Connect an integration → integration_connected with integration_type
    • Invite a team member → team_member_invited with invitee_role
    • Export responses → responses_exported with format
    • Submit survey responses → survey_response_received fires on 1st and every 100th
  • Unset POSTHOG_KEY and verify all flows work normally with no errors (graceful no-op)
  • Verify no console.log statements, no client-side bundle pollution (server-only import)

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read How we Code at Formbricks
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand bits
  • Ran pnpm build
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues
  • First PR at Formbricks? Please sign the CLA! Without it we wont be able to merge it 🙏

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Formbricks Docs if changes were necessary

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 1, 2026

Walkthrough

This 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 posthog-node dependency is introduced and configured as an external server package.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: custom posthog events' clearly and concisely summarizes the main change: adding custom PostHog event tracking to the codebase.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering what the PR does, implementation details, testing instructions, and a completed checklist with all required items marked.

✏️ 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6c34c31 and e1d681f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/actions.ts
  • apps/web/app/(app)/environments/[environmentId]/workspace/integrations/actions.ts
  • apps/web/app/api/(internal)/pipeline/route.ts
  • apps/web/app/setup/organization/create/actions.ts
  • apps/web/lib/posthog/capture.ts
  • apps/web/lib/posthog/index.ts
  • apps/web/lib/posthog/server.ts
  • apps/web/modules/auth/lib/authOptions.ts
  • apps/web/modules/auth/signup/actions.ts
  • apps/web/modules/ee/billing/actions.ts
  • apps/web/modules/ee/sso/lib/sso-handlers.ts
  • apps/web/modules/organization/settings/teams/actions.ts
  • apps/web/modules/survey/components/template-list/actions.ts
  • apps/web/modules/survey/components/template-list/index.tsx
  • apps/web/modules/survey/editor/actions.ts
  • apps/web/next.config.mjs
  • apps/web/package.json
  • packages/cache/src/cache-keys.ts

Comment thread apps/web/app/setup/organization/create/actions.ts
Comment thread apps/web/lib/posthog/server.ts
Comment thread apps/web/modules/auth/lib/authOptions.ts
Comment thread apps/web/modules/auth/lib/authOptions.ts Outdated
@jobenjada jobenjada removed their request for review April 1, 2026 13:53
Copy link
Copy Markdown
Member

@Dhruwang Dhruwang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good 🚀 , can you look at the comments and also this one
Screenshot 2026-04-02 at 10 36 36 AM

Comment thread apps/web/modules/auth/lib/authOptions.ts Outdated
Comment thread apps/web/modules/survey/editor/actions.ts Outdated
@pandeymangg pandeymangg requested a review from mattinannt April 3, 2026 06:03
Comment thread apps/web/app/api/(internal)/pipeline/route.ts Outdated
mattinannt
mattinannt previously approved these changes Apr 7, 2026
harshsbhat
harshsbhat previously approved these changes Apr 9, 2026
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 9, 2026

@pandeymangg pandeymangg added this pull request to the merge queue Apr 9, 2026
Merged via the queue into main with commit 3d16e85 Apr 9, 2026
17 checks passed
@pandeymangg pandeymangg deleted the feat/custom-posthog-events branch April 9, 2026 05:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants