posthog review
posthog 7.44.1 is PostHog's Python client for product analytics, server-side feature flags, person and group properties, exception events, and optional instrumentation around AI or MCP code. `capture()` normally hands an event to an in-memory delivery queue, while flag methods either contact the service or use definitions polled into the process. The new 7.44.0 option can copy an active OpenTelemetry trace ID and span ID onto captured events; it is off by default and explicit event properties win. Patch 7.44.1 also makes local `is_set` and `is_not_set` flag rules behave correctly when the caller supplies only part of the property context. Our measured 7.42.0 import took 0.93 seconds.
posthog 7.42.0 installed in 0.3 seconds and used 6 MB across 9 packages in our sandbox, with 0 audit findings and a working 0.93-second import. The current 7.44.1 client belongs in services already committed to PostHog, provided every process has an explicit queue-drain path.
We installed it
| Install | ✓ · 0.3s | 9 packages on disk · 6 MB |
| Import | ✓ | import posthog in 0.93s · pure Python · py.typed · requires Python >=3.10 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does posthog install cleanly?
Yes. In a fresh container with an empty cache, pip install posthog finished in 0.3s, leaving 9 packages and 6 MB on disk. pip-audit reported no known vulnerabilities.
What does posthog need to run?
Python >=3.10, and nothing compiled: it is pure Python. In our run import posthog succeeded in 0.93s, and the package ships py.typed for type checkers.
posthog or sentry-sdk: which should you use?
sentry-sdk: Use it when exception grouping, tracing, releases, and operational alerts matter more than product funnels. posthog 7.42.0 installed in 0.3 seconds and used 6 MB across 9 packages in our sandbox, with 0 audit findings and a working 0.93-second import.
When should you not use posthog?
Your analytics destination is undecided or must remain portable. Event capture, feature flags, and identity semantics in this package are specific to PostHog.
Use it if
- PostHog already receives browser events and payments, jobs, or webhooks need to use the same distinct IDs and event taxonomy.
- Backend branches depend on PostHog Boolean or multivariate flags, including locally evaluated definitions.
- Django, Flask, Celery, model-provider calls, or an MCP server should report through vendor-maintained integrations.
- The process has a reliable shutdown point where its queued analytics events can be drained.
- Your analytics destination is undecided or must remain portable. Event capture, feature flags, and identity semantics in this package are specific to PostHog.
- A function may exit immediately after one capture and cannot call `flush()` before its runtime is frozen. The default queue can retain the last events in memory.
- All dependencies called from an asyncio handler must be nonblocking. Core transport and feature-flag methods in this client are synchronous.
- Exception triage, release health, alerts, and issue grouping are the primary requirement. A dedicated error tracker such as Sentry covers that workflow more directly.
- You require a quiet SDK surface. The 7.x changelog has frequent changes across delivery, flags, AI capture, MCP instrumentation, metrics, and exception handling.
Setup reality
We installed posthog 7.42.0 in a clean Python 3.12 Bookworm sandbox. The install took 0.3 seconds, put 9 packages on disk, and used 6 MB. import posthog completed in 0.93 seconds. That pure-Python wheel requires Python 3.10 or later, ships py.typed, and declares 52 dependencies when optional requirements are counted. pip-audit found 0 known vulnerabilities, while the package metadata did not identify a license. These measurements are for 7.42.0, not current 7.44.1.
A project API key authorizes ingestion, and host must match the project's region. Local flag polling needs the more privileged server-side secret key; personal_api_key remains as a deprecated alias. Keep that credential out of browser code. Choose either one Posthog instance or module-level configuration so separate queues do not acquire different hosts and settings. Capture failures are intentionally quiet, so wire debug logs or an error callback when proving network delivery.
capture() queues events for a worker until the count or timer triggers a batch. Call flush() at a job boundary and shutdown() once during service termination. After shutdown, new captures are dropped. Forked and preloaded worker servers need a lifecycle test so threads and context belong to the child process. A before_send callback can redact or reject an event, and any exception raised by that callback causes the event to be dropped.
Local evaluation only works when the process has current definitions and the properties referenced by each rule. Since 7.43.0, a key absent from local definitions falls back to a remote request unless only_evaluate_locally=True. Version 7.44.1 corrects partial-context handling for is_set and is_not_set. Version 7.44.0 can attach OpenTelemetry trace context when capture_trace_context=True; install the otel extra and decide whether correlating IDs should leave the service before enabling it.
Patterns
Create one EU client with bounded batching configure-regional-client
import os
from posthog import Posthog
posthog = Posthog(
os.environ['POSTHOG_PROJECT_KEY'],
host='https://eu.i.posthog.com',
flush_at=100,
flush_interval=5.0,
)Use the ingestion host shown for the project region. A request accepted by a different regional endpoint will not appear in the intended project.
Record an event from backend code capture-product-event
posthog.capture(
'subscription upgraded',
distinct_id='user-42',
properties={
'plan': 'pro',
'source': 'billing-webhook',
},
)Current 7.x takes the event name first and the identity as a keyword. Reuse the frontend's stable distinct ID to keep one person history.
Set mutable and first-touch person fields write-person-properties
posthog.set(
distinct_id='user-42',
properties={'plan': 'pro'},
)
posthog.set_once(
distinct_id='user-42',
properties={'signup_source': 'organic'},
)`set()` can replace a value later. `set_once()` preserves the first accepted value and suits acquisition fields.
Read Boolean and multivariate flags check-feature-flag
enabled = posthog.feature_enabled('new-dashboard', 'user-42')
variant = posthog.get_feature_flag(
'pricing-test',
'user-42',
person_properties={'plan': 'pro'},
)
if enabled:
show_dashboard()
if variant == 'annual-first':
show_annual_price()Supply every property referenced by the flag rules. Missing context can force server evaluation or select a different variant.
Evaluate several flags once per request evaluate-flag-snapshot
flags = posthog.evaluate_flags(
'user-42',
person_properties={'plan': 'enterprise'},
flag_keys=['new-dashboard', 'pricing-test'],
)
if flags.is_enabled('new-dashboard', default_value=False):
show_dashboard()
posthog.capture('dashboard viewed', distinct_id='user-42', flags=flags)A snapshot avoids separate evaluation for each branch and can attach the values used by the request to its event.
Poll definitions and forbid network fallback force-local-flag-evaluation
posthog = Posthog(
os.environ['POSTHOG_PROJECT_KEY'],
secret_key=os.environ['POSTHOG_SECRET_KEY'],
host='https://eu.i.posthog.com',
enable_local_evaluation=True,
poll_interval=30,
)
value = posthog.get_feature_flag(
'new-dashboard',
'user-42',
person_properties={'plan': 'pro'},
only_evaluate_locally=True,
)Without `only_evaluate_locally=True`, 7.43.0 and later can contact the server when a key is missing from polled definitions.
Correlate events with an active OpenTelemetry span attach-trace-context
posthog = Posthog(
os.environ['POSTHOG_PROJECT_KEY'],
host='https://us.i.posthog.com',
capture_trace_context=True,
)
with tracer.start_as_current_span('create-order'):
posthog.capture('order created', distinct_id='user-42')Version 7.44.0 adds this opt-in path. Install the `otel` extra; explicit `$trace_id` and `$span_id` event properties take precedence.
Update a company and attach it to an event associate-group
posthog.group_identify(
group_type='company',
group_key='acme',
properties={'plan': 'enterprise', 'seats': 80},
)
posthog.capture(
'report exported',
distinct_id='user-42',
groups={'company': 'acme'},
)`group_identify()` updates the group profile. The `groups` argument on capture connects this event to that company.
Apply identity and tags inside one request scope-request-context
from posthog import capture, identify_context, new_context, tag
with new_context():
identify_context('user-42')
tag('request_id', request_id)
tag('tenant', 'acme')
capture('invoice generated')
capture('email queued')Start a new context for every request or job. Reusing one can carry a previous tenant's identity into later events.
Send a caught exception and preserve failure capture-handled-error
try:
charge(order)
except PaymentError as error:
posthog.capture_exception(
error,
distinct_id='user-42',
properties={'order_id': order.id},
)
raiseRaising again keeps the application's error path intact. Analytics capture should not turn a failed payment into a successful return.
Remove secrets before an event reaches the queue redact-event-properties
def redact(event):
properties = event.get('properties', {})
properties.pop('authorization', None)
if 'email' in properties:
properties['email'] = '[redacted]'
return event
posthog.before_send = redactReturning `None` drops the event. An exception inside `before_send` also drops it, so test redaction code without network dependencies.
Drain a short-lived process explicitly flush-before-exit
try:
posthog.capture(
'nightly import finished',
distinct_id='system',
properties={'rows': imported_rows},
)
finally:
posthog.flush(timeout_seconds=5)
posthog.shutdown()`flush()` waits for queued delivery and keeps the client usable. `shutdown()` is terminal; later captures are discarded.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| sentry-sdk | PyPI | Use it when exception grouping, tracing, releases, and operational alerts matter more than product funnels. |
| mixpanel | PyPI | Use it when Mixpanel is already the chosen product analytics and identity system. |
| analytics-python | PyPI | Use Segment's client when one event stream must route to several downstream tools. |
| opentelemetry-sdk | PyPI | Use it for vendor-neutral service traces and metrics rather than product analytics and experiments. |
More utils guides
lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

