Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions context/commandments.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ commandments:
- 'In CLIs and scripts: MUST call posthog.shutdown() before exit or all events are lost'
- Always use the Posthog() class constructor (instance-based API) instead of module-level posthog.api_key config
- Always include enable_exception_autocapture=True in the Posthog() constructor to automatically track exceptions
- PosthogFlaskIntegration (posthog.integrations.flask), PosthogASGIMiddleware (posthog.integrations.asgi), and the Django REST Framework exception handler (posthog.integrations.drf) need posthog 7.69.0 or later - when you add one, require at least that version
- NEVER send PII in capture() event properties — no emails, full names, phone numbers, physical addresses, IP addresses, or user-generated content
- PII belongs in identify() person properties, NOT in capture() event properties. Safe event properties are metadata like message_length, form_type, boolean flags.
- Register posthog_client.shutdown with atexit.register() to ensure all events are flushed on exit
Expand Down Expand Up @@ -141,19 +142,20 @@ commandments:

django:
- Add 'posthog.integrations.django.PosthogContextMiddleware' to MIDDLEWARE, after AuthenticationMiddleware, it auto-extracts tracing headers and captures exceptions
- Initialize PostHog in AppConfig.ready() with api_key and host from environment variables
- Pass the Posthog() client to the middleware with the POSTHOG_MW_CLIENT setting, as the Django docs show - do not assign django.conf.settings at runtime, such as in AppConfig.ready()
- The middleware identifies the request context from the X-POSTHOG-DISTINCT-ID header or the authenticated user's pk, so in a view where the user is already logged in a plain capture() is already attributed - do not wrap it in a context of its own
- The middleware reads the user once, before the view runs, so a login request's context is identified as whoever the user was beforehand - nobody - and calling login() does not update it. A bare capture there is personless. Hook Django's user_logged_in signal and call identify_context(str(user.pk)) inside it: the signal runs inside the login request, so it fixes the ambient context and every later capture in that request is attributed. Logout views need no special handling, the user is still authenticated when the middleware runs - capture before calling logout()
- Do NOT create custom middleware, distinct_id helpers, or conditional checks - the SDK handles these

flask:
- Initialize PostHog globally in create_app() using posthog.api_key and posthog.host (NOT per-request)
- Manually capture exceptions with `posthog.capture_exception(e)` for error tracking since Flask has built-in error handlers
- Register PosthogFlaskIntegration with one Posthog() client, as the Flask docs show (NOT per-request)
- Do NOT add an @app.errorhandler(Exception) to call capture_exception() - the integration already captures unhandled route exceptions
- Blueprint registration happens AFTER PostHog initialization in create_app()

fastapi:
- Initialize PostHog in the lifespan context manager on startup using posthog.api_key and posthog.host
- Add PosthogASGIMiddleware, as the ASGI section of the Python docs shows. Create the client where you build the app, not in the lifespan - middleware cannot be added once the app starts
- Call posthog.flush() in the lifespan shutdown to ensure all events are sent before the app exits
- Do NOT call capture_exception() in an @app.exception_handler(Exception) catch-all - the middleware already captures those exceptions
- Use Pydantic Settings with @lru_cache decorator on get_settings() for caching and easy test overrides
- Use FastAPI dependency injection (Depends) for accessing current_user and settings in route handlers
- Use the same context API pattern as Flask/Django (with new_context(), identify_context(user_id), then capture())
Expand Down
20 changes: 10 additions & 10 deletions context/skills/posthog-best-practices/description.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ If the project uses Django:
context identified as whoever they were beforehand, which is nobody on a login.
There, open a fresh context and identify it after the auth state changes, or the
event is attributed to the wrong person.
- Initialize PostHog in `AppConfig.ready()` from env vars.
- Pass the `Posthog()` client to the middleware with `POSTHOG_MW_CLIENT`, as the
Django docs show.
- Do not write custom middleware or `distinct_id` helpers first.
- For management commands or other short-lived processes, call the underlying Python client's `posthog.shutdown()` before exit.
- If this applies, consult [the Django docs](https://posthog.com/docs/libraries/django).
Expand All @@ -169,22 +170,21 @@ If the project uses Django:

If the project uses Flask:

- Initialize PostHog globally in `create_app()` before blueprint registration.
- No middleware ships for Flask, so nothing binds identity for you: open a fresh
context at the top of each route and identify it there, preferring the
authenticated user id and falling back to the incoming `X-POSTHOG-DISTINCT-ID`.
- Call `posthog.capture_exception(e)` from centralized error handlers.
- Initialize PostHog in `create_app()` before blueprint registration, and
register `PosthogFlaskIntegration`, as the Flask docs show. It opens a context
for each request and captures route exceptions. Identify the user with
`identify_context(...)` in the route.
- For CLI commands, jobs, or other short-lived processes, call the underlying Python client's `posthog.shutdown()` before exit.
- If this applies, consult [the Flask docs](https://posthog.com/docs/libraries/flask).

### FastAPI

If the project uses FastAPI:

- Initialize PostHog in lifespan startup and call `posthog.shutdown()` in lifespan shutdown.
- No middleware ships for FastAPI either — bind identity per request yourself, in
a dependency or middleware that opens a context and identifies it, rather than
at each capture.
- Add `PosthogASGIMiddleware`, as the Python docs show, and call
`posthog.shutdown()` in lifespan shutdown. The middleware opens a context for
each request and captures route exceptions. Identify the user in a dependency
rather than at each capture.
- Prefer Pydantic Settings with `@lru_cache`.
- Use `Depends` for current user and settings in handlers.
- If this applies, consult [the Python docs](https://posthog.com/docs/libraries/python).
Expand Down
Loading