Session Replay troubleshooting

This page covers troubleshooting for Session Replay. For setup, see the installation guides.

Have a question? Ask PostHog AI

Update posthog-js

Having trouble with recordings? The most common solution is to update posthog-js to the latest version.

We're always making improvements to the recordings feature, so you'll want to make sure that you're running the latest version of posthog-js on your website.

To check the version that you're using, you can run the following in your browser console:

JavaScript
window.posthog.LIB_VERSION

You can also check using the SDK doctor tool.

Check the replay status on your events

Recent versions of posthog-js attach diagnostic properties to every event that describe what Session Replay was doing at the moment the event was captured. Reading these is usually the fastest way to figure out why a session wasn't recorded before you start changing settings.

You can query them from the SQL editor for a specific session ID:

SQL
SELECT
properties.$has_recording AS has_recording,
properties.$recording_status AS recording_status,
properties.$session_recording_start_reason AS start_reason,
properties.$sdk_debug_recording_script_not_loaded AS script_blocked,
properties.$sdk_debug_replay_url_trigger_status AS url_trigger,
properties.$sdk_debug_replay_event_trigger_status AS event_trigger,
properties.$sdk_debug_replay_linked_flag_trigger_status AS flag_trigger,
properties.$replay_sample_rate AS sample_rate,
properties.$lib_version AS sdk_version
FROM events
WHERE $session_id = '<your-session-id>'
ORDER BY timestamp DESC
LIMIT 1

Always look at the latest event in the session – early events may not yet reflect the final state (for example, $has_recording only flips to true once PostHog has confirmed at least one recording chunk was ingested).

The three core signals

These three properties together describe replay status. Read them as a set, not in isolation.

PropertyWhat it tells you
$has_recordingtrue once PostHog has confirmed a recording exists for the session. The strongest positive signal.
$recording_statusThe SDK's current state: active, buffering, disabled, sampled, or paused.
$session_recording_start_reasonWhy recording started (or didn't). Useful for distinguishing "we chose not to record" from "we tried but couldn't".

$recording_status values

ValueMeaning
activeThe SDK is recording and producing snapshots.
bufferingThe SDK initialized but is waiting for a trigger, minimum duration threshold, or remote config before producing snapshots.
disabledRecording is turned off – either in your project replay settings or via SDK config at runtime.
sampledThis session was included by the configured replay sample rate – recording started.
pausedRecording is temporarily paused for this session (consent gate, programmatic pause(), or a max-duration cap).

$session_recording_start_reason values

ValueMeaning
recording_initializedRecording started as soon as the SDK initialized.
sampling_overrideRecording started because the session was included by the sampling rules.
sampled_outRecording was prevented because the session was excluded by sampling.
linked_flag_matchRecording started because a linked feature flag matched.

Reading the combination

$has_recording$recording_status$session_recording_start_reasonWhat it means
trueanyanyA recording exists. If you can't find it, the issue is UI filtering, retention, or it's still processing.
null / falseactive or sampledrecording_initialized / sampling_override / linked_flag_matchRecording is running but PostHog hasn't confirmed ingestion yet, check a later event.
null / falsebufferinganyThe SDK is waiting on a trigger or duration threshold, check the trigger status properties.
null / falsedisabledanyReplay is off, either in project settings or via SDK config like disable_session_recording: true.
null / falseanysampled_outYour sample rate excluded this session – increase the sample rate or use a trigger to guarantee capture for important flows.
null / falsepausedanyRecording was paused mid-session – usually a consent flow or a programmatic posthog.sessionRecording.pause() call.

Lifecycle

A normal session's $recording_status typically progresses like this:

text
disabled ──── (config or setting turns replay off)
buffering ──► active ──► (recording captured, $has_recording flips to true on later events)
│ │
│ └──► paused ──► active (consent flow, programmatic control)
└──► (session ends before a trigger matches, buffer discarded, no recording stored)

sampled is an alternative entry point meaning "this session was rolled into the sample" and it behaves like active from a capture standpoint.

Other useful diagnostic properties

PropertyWhat it tells you
$sdk_debug_recording_script_not_loadedtrue if the recorder.js script failed to load. Usually caused by an ad blocker or CSP
$sdk_debug_replay_url_trigger_status
$sdk_debug_replay_event_trigger_status
$sdk_debug_replay_linked_flag_trigger_status
Trigger states: trigger_disabled (none configured), trigger_pending (configured, waiting to match), or trigger_matched (fired).
$sdk_debug_replay_internal_buffer_length
$sdk_debug_replay_flushed_size
If buffer_length keeps growing across the session's events while flushed_size stays at 0, the POST /s/ upload is being blocked – usually an ad blocker or a misconfigured reverse proxy.
$replay_sample_rate
$replay_minimum_duration
The sample rate (0.0 to 1.0) and minimum duration (ms) in effect at capture time.

If you're on an older SDK version, some of these properties won't be present, null means "unknown", not "false". Upgrading posthog-js will give you a much fuller picture.

Recordings are not being captured

There are a few common reasons that you may not see recordings appear in your project.

1. Authorized domains for recordings

Authorized domains for recordings is deprecated, you should use URL triggers instead. Read more in our replay controls doc.

In your project replay settings, for older projects, there is a section for 'Authorized domains for replay'. This is the list of domains where PostHog will capture recordings. You should make sure it's not too restrictive.

For example, you may have https://www.example.com in the list. This will stop PostHog from capturing recordings on https://example.com.

If no domains are set here, PostHog will capture recordings on all domains.

2. posthog-js configurations

If you had previously disabled session recordings, you may have set the disable_session_recording option to true in posthog-js.

To re-enable session recordings you want to either remove the disable_session_recording option or set it to false.

You can read more about posthog-js configurations here.

3. Content security policy

When recordings are enabled, posthog-js will fetch a recorder.js script from the PostHog server. (This is not included in the default posthog-js to minimize the default bundle size)

Depending on your content security policy, this script may be blocked. If you have a default-src, script-src, script-src-elem, or connect-src directive in your CSP, you may need to ensure that PostHog domains are permitted (see CSP documentation).

If PostHog is being blocked by your content security policy, you should see an error message in your developer console with more details.

4. Ad/tracking blockers

Some ad/tracking blockers will block PostHog from fetching posthog-js. If you're testing your app locally, you may need to disable any ad/tracking blockers that you're running in your browser.

Deploying a reverse proxy is the best way to avoid tracking blockers intercepting calls to PostHog.

Replay of recording looks incorrect or buggy

If you're having issues with recordings not looking correct, there are a few things you can:

Ensure your website permits PostHog CORS

Most assets rendered on your website or app are captured inline – meaning we keep a copy of the icon, CSS, etc. to use when replaying. For some assets however, this is either too difficult or too costly to do, so instead we load them directly from the original web server (just like your website does). This can lead to CORS issues where the web server does not permit us.posthog.com (or eu.posthog.com) to load the required asset. Unfortunately we cannot detect when this happens, but the browser's developer tools (Option + ⌘ + J, or Shift + CTRL + J) will log when these errors occur saying something like:

Access to font at 'https://yourwebsite.com/fonts/MyriadPro-Bold.woff2' from origin 'https://us.posthog.com has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
@ GET https://yourwebsite.com/fonts/MyriadPro-Bold.woff2 net: :ERR_FAILED 200

If you see errors like this, please add us.posthog.com (or eu.posthog.com) to the list of permitted domains in your web server's CORS configuration.

Adjust the crossorigin attribute on stylesheets

If the crossorigin attribute is not set on link elements a Chrome specific protection will prevent programmatic access to stylesheet rules (e.g. stylesheet.cssRules throws an exception). This means that styles on your site render as expected but they cannot be captured by PostHog in order to recreate the session during playback.

Setting crossorigin="anonymous" is the safest value you can set for the parameter as no user credentials are added to the request.

Ensure assets are imported from the base URL in Svelte

By default, base and assets imported from $app/paths in Svelte use relative asset paths during server-side rendering.

For apps with SSR enabled you should set relative to false in the paths object of your config. This ensures all asset URLs will correctly be resolved against the base URL rather than the current page.

Images and videos do not appear in the replay

PostHog captures session replays by capturing the DOM, not videos or screenshots. When you watch a replay, PostHog tries to reconstruct the DOM.

If the DOM contains images or other media assets referenced by a URL that is signed, expired, or otherwise not available, the replay will not be able to reconstruct the DOM correctly. A workaround to make replays more accurately represent the original session is to fallback to a placeholder if the asset is no longer available.

Unable to filter by user or page properties

Some of the filtering options in session replays depend on Product Analytics events.

To enable all filtering options, we recommend capturing at least one event per session. Most commonly identifying users via identify(). This makes sure you can use all of the power of filtering by person and event properties.

Since these events contribute to the Product Analytics usage and billing limits, if you exceed your Product Analytics monthly limit, the recordings captured after that (until new billing period starts or limit is increased) won't be possible to filter by these properties.

Internal users filtering or cohorts-based filters don't work

If you use cohorts to filter sessions, or add a cohort condition into the Filter out internal and test users setting – it can work incorrectly (or not work at all) while cohorts are calculating.

To check, you can go to "People > Cohorts" and see if any of them are in "calculating" status. 99% of the time they will be available in a couple of minutes, and if not (got stuck) – you can ask PostHog support to recalculate them for you manually.

Filtering can't find my custom events

The most common reason for this is that you're trying to filter by events that weren't sent by the web SDK. For example, from another tool like Segment or one of our backend SDKs.

Session Replay relies on the $session_id property added to events by the web SDK to link events to sessions.

To workaround this, you can use the posthog.onSessionId method in the web SDK to register a callback that will be called each time the session ID changes. You can send this to your backend and add the $session_id property to your backend events.

Recording not found

There are several valid reasons why recordings aren't captured. To keep the site fast we don't actively confirm a recording has been captured before showing the "view recording" button.

For example:

  • If an event is captured before your minimum recording duration setting there is no guarantee that the session lasted long enough to start recording.
  • If a user closes the browser tab or window there may not be enough time to send the data to the backend
  • If you have conditional triggers set we don't send data to the backend until the trigger conditions are met

Person not found

If you are able to capture a replay, but the entry says "person not found," this is replay has been captured from an anonymous user and a person profile has not been created for them.

To fix this, either call a method that creates a person profile (like identify()) or set the person_profiles value to always. See our doc on anonymous vs identified events for more.

Browser freezes or crashes when recordings are enabled

Some Cookie banners or "CMP"s modify the webpage in reaction to any changes to cookies, which can cause an infinite loop. If you are using a tool like this and experience the webpage becoming unresponsive or slow then try configuring PostHog to use persistence: "localStorage+cookie". This will use localStorage for the majority of storage needs, bypassing the infinite loop issue sometimes caused by cookie management tools.

Enable recordings when URL matches not working as expected

The Enable recordings when URL matches setting is only supported in the web SDK version 1.171.0 and above. If your recordings aren't being recorded as expected, even when the URL matches your regex, it's likely due to an outdated SDK version.

Make sure you're using version 1.171.0 or newer to ensure this feature works correctly.

Network payload capture is not capturing headers

Browser do not automatically allow JS to read headers from cross-origin requests. So, when making a request from app.mywebsite.example to api.mywebsite.example headers are not available to be copied and reported.

You need to add Access-Control-Expose-Headers: * as a header on responses from api.mywebsite.example to allow all headers to be read cross origin. You can specify which headers you want to be available Access-Control-Expose-Headers: Content-Encoding, X-Correlation-Id if you don't want all headers to be accessible.

Recorder Limitations

Due to the complexity of browser rendering and replay, some web resources are unable to be recorded or played back. These include:

  • Object elements – Most object elements cannot be played back. Non-HTML native plugins such as Flash or Silverlight cannot be recorded
  • Heavy animations – Some heavier animations such as SVG or Lottie animations may be throttled or ignored entirely due to the performance overhead in recording and replaying
  • Iframes – iframes from the origins under your control can be recorded and played back. See iframe recording for more info.

Angular performance

It is sometimes necessary to run Session Replay outside of the Angular zone. See the PostHog Angular docs for more detail

Report your specific issue

To report a specific problem, you can open a GitHub issue. To help us figure it out as quickly as possible, please include the following information:

  • The URL of the page that you're trying to record
  • The version of PostHog that you're using
  • The version of posthog-js that you're using
  • Details about the specific issue with your recording (e.g. how it looks and how it should look)
  • If you're on PostHog Cloud, a link to the specific recording
  • Any unique details about your website (e.g. the frameworks that you're using etc.)

Solved community questions

Community questions

Was this page useful?

Questions about this page? or post a community question.