Migrating to PostHog Python SDK V6
Jul 29, 2025
On this page
- Overview
- New feature: contexts
- New: Django contexts middleware
- Breaking change: identification
- Breaking change: capture method signature
- Breaking change: Django error tracking integration
- Migrating from V5 to V6
- 1. Replace identify() calls
- 2. Replace screen() and page() calls
- 3. Update capture() and capture_exception() calls
- 4. Replace the Django error handling integration
- 5. Adopt contexts
- Further reading
If you're just looking for instructions to upgrade to
6.x
, skip directly to the migration guide.
Version 6.x
of the PostHog Python SDK introduces a new context()
API for scope-based state sharing across captures along with a series of breaking changes. This guide walks through changes introduced and how to migrate from 5.x
to 6.x
.
Overview
Here are the major changes from 5.x
to 6.x
:
Type | Summary |
---|---|
New | Scoped contexts for state management across event captures |
New | Django contexts middleware wraps all requests in a context, extracts user and session information from headers automatically |
Breaking | The identify() method is deprecated in favor of contexts |
Breaking | Deprecated page() and screen() methods. Prefer use of capture() on the backend or capturing page/screen view events on the client-end |
Breaking | Order of arguments updated for capture-related methods. You must update calls to use kwargs before upgrading |
Breaking | Capture methods now return the UUID of the captured event, rather than the full event object |
Breaking | Deletes unmaintained exception capture integrations, which is replaced by the general purpose Django middleware |
You should always pin versions using the compatible release clause to avoid auto-upgrading to an incompatible version accidentally.
New feature: contexts
Version 6.x
introduces contexts. Events captured under the same context scope will share properties set on the context. This can be used to identify who, where, and when an event is triggered.
This is very convenient for server-side use cases like Django apps, where each request might be handled for a different user.
from posthog import new_context, tag, set_context_session, identify_contextwith new_context():# Identify user and sessionset_context_session(session_id)identify_context(user_id)# Set properties share by events in the contexttag("transaction_id", "abc123")# This event is captured with the distinct ID, session ID, and tags set aboveposthog.capture("order_processed")
Contexts can be passed down the call tree. You can iteratively add tags to the context to keep track of events leading up to a capture call. For example:
from django.http import HttpResponsefrom posthog import new_context, tag, set_context_session, identify_context, capturefrom posthog.contexts import get_tagsdef handle_order(request):with new_context():set_context_session(session_id)identify_context(user_id)validate_order()return HttpResponse("Hello, World!")def validate_order():tag("order_steps", ["validate_order"])process_payment()def process_payment():tag("order_steps", [*get_tags()["order_steps"], "process_payment"])update_inventory()def update_inventory():tag("order_steps", [*get_tags()["order_steps"], "update_inventory"])capture("order_completed")
When capture
is finally called, the order_completed
event will have a custom event property called order_steps
with a list of values ["validate_order", "process_payment", "update_inventory"]
.
Contexts can also be nested. This means that:
- Parent context values are shared with child contexts.
- Child contexts can override parent context value within the child context's scope.
- When a child context exits, the overridden parent context is restored.
from posthog import new_context, tagwith new_context():tag("some-key", "value-1")tag("some-other-key", "another-value")with new_context():tag("some-key", "value-2")# This event is captured with some-key="value-2" and some-other-key="another-value"posthog.capture("order_processed")# This event is captured with some-key="value-1" and some-other-key="another-value"posthog.capture("order_processed")
Learn more about contexts in the Python SDK docs.
New: Django contexts middleware
The new contexts middleware for Django wraps every request in a context. The context will automatically capture the following information:
Property | Source |
---|---|
Session ID | From X-POSTHOG-SESSION-ID header, if present |
Distinct ID | From X-POSTHOG-DISTINCT-ID header, if present |
$current_url | Current URL |
$request_method | Request method |
If you're using the PostHog JavaScript web SDK on the client-side, enabling tracing headers will populate these headers automatically.
This middleware will also automatically capture exceptions. You can learn more about this middleware in the Django integration docs.
Breaking change: identification
The identify()
method no longer exists in the Python SDK. On client SDKs, identify()
declares which user is using the device. This pattern doesn't apply to server SDKs, where each request handled could belong to a different user.
The preferred way to identify events in 6.x
is to use contexts. For example, when handling requests from the client, you can parse the distinct ID and session ID from the incoming request and set this information on a context.
# Using contextsfrom posthog import new_context, identify_context, set_context_sessionfrom posthog.contexts import scoped@scoped(fresh=True)def handle_request(request):identify_context(request.get_header('X-POSTHOG-DISTINCT-ID'))set_context_session(request.get_header("X-POSTHOG-SESSION-ID"))posthog.capture('event_name')
All events captured under this context's scope, which is all events captured during the handling of this request, will be identified by the distinct ID of the user and session ID.
Alternatively, you can specify distinct ID explicitly during a capture and set persons properties:
posthog.capture('event_name',distinct_id='user-distinct-id',properties={'$set': {'name': 'Max Hedgehog'},'$set_once': {'initial_url': '/blog'}})
Breaking change: capture method signature
Distinct ID is no longer a required first argument for capture methods like capture()
and capture_exception()
. This is a consequence of preferring contexts to identify events. When you specify a distinct ID in a identify_context()
, you won't need to pass it in again when capturing events.
Here's a before and after of what this might look like:
# BEFORE: capturing in 5.xposthog.capture('distinct_id_of_the_user', 'user_signed_up')# AFTER: capturing in 6.xwith new_context():identify_context('distinct_id_of_the_user')posthog.capture('user_signed_up')
You can still capture events with an explicit distinct_id
like before, but it must be passed in as a keyword argument:
# You can also capture events with a specific distinct_idposthog.capture('some-custom-action', distinct_id='distinct_id_of_the_user')
Capture methods also now return the event UUID instead of the full event object. You can find the exact function signature in the Python SDK reference.
Breaking change: Django error tracking integration
You might be using the old Django error tracking integration like this in 5.x
:
from posthog import Posthogfrom posthog.exception_capture import IntegrationsPosthog("<ph_project_api_key>", enable_exception_autocapture=True, exception_autocapture_integrations = [Integrations.Django])
This is deprecated and no longer necessary in 6.x
, instead, the general purpose context middleware also handles error capturing.
Migrating from V5 to V6
If you're planning to upgrade from 5.x
to 6.x
of the Python SDK, here are the changes you need to make to complete the migration. You can do this by installing "posthog~=6.0.0"
using your package manager of choice.
1. Replace identify()
calls
The identify()
method is removed. You can instead set properties during capture or explicitly use set()
to set persons properties to create person profiles.
Before:
posthog.identify(distinct_id='user123',properties={'name': 'John Doe', 'email': 'john@example.com'})
Now:
# Captures identified events if the current context is identified# Or if you pass a distinct ID explicitly.posthog.capture('event_name',distinct_id='user-distinct-id',properties={'$set': {'name': 'Max Hedgehog'},'$set_once': {'initial_url': '/blog'}})# Using contextsfrom posthog import new_context, identify_contextwith new_context():identify_context('user-distinct-id')posthog.capture('event_name')# Or set explicitlyposthog.set(distinct_id='userb',properties={'name': 'John Doe', 'email': 'john@example.com'})
2. Replace screen()
and page()
calls
The methods screen()
and page()
are removed. They are aliases for the capture()
method. Instead, use capture()
explicitly to capture $pageview
or $screen
events. You can check the activity tab to see what properties are captured in a $pageview
event.
Before:
posthog.page(distinct_id=user_id,url=url,properties={...})
Now:
from posthog import identify_context, captureidentify_context(user_id)capture('$pageview',properties={'$current_url': url,...})
3. Update capture()
and capture_exception()
calls
The capture()
and capture_exception()
methods now take keyword arguments instead of positional arguments. The distinct ID is now optional.
Before:
posthog.capture("user123", "button_clicked", {"button_id": "123"})
Now:
### New pattern with contextswith posthog.new_context():posthog.identify_context("user123")posthog.tag("button_id", "123")# The event name is the first argument, and can be passed positionally, or as a keyword argument in a later positionposthog.capture("button_pressed")# Or explicitly state distinct IDposthog.capture(distinct_id="user123", event="button_clicked", properties={"button_id": "123"})
4. Replace the Django error handling integration
The exception_capture
module is removed. Instead, use the context middleware to capture exceptions.
Before:
from posthog import Posthogfrom posthog.exception_capture import IntegrationsPosthog("<ph_project_api_key>", enable_exception_autocapture=True, exception_autocapture_integrations = [Integrations.Django])
Now:
# In your Django `settings.py` fileMIDDLEWARE = [# ... other middleware'posthog.integrations.django.PosthogContextMiddleware',# ... other middleware]POSTHOG_MW_CAPTURE_EXCEPTIONS = True
5. Adopt contexts
Contexts are new. While they're not a breaking change, we recommend that you adopt the contexts pattern for easier management of state between captured events. See the context docs to learn more.
Further reading
Questions? Ask Max AI.
It's easier than reading through 698 pages of documentation