Loading tutorials…
Loading tutorials…
Session replay is the most valuable PostHog feature for debugging product UX — and the most dangerous if you skip the masking step. This walks through enabling replay, configuring DOM-level privacy, controlling storage cost, and the compliance checklist.
Who this is forProduct managers and engineers who need to see what users actually do — bug reports, conversion drop-offs, onboarding friction. Especially important if you handle any sensitive data (payments, health, financial, B2B with NDA-covered usage).
What you'll need
Step 1
Set Project Settings → Session Replay → Privacy → Mask all input fields to TRUE. Add `ph-no-capture` class to sensitive DOM elements. THEN turn replay on.
In PostHog → Project Settings → Session Replay → Privacy.
Set "Mask all input fields by default" to ON. This replaces every `<input>` with asterisks in recordings. You can selectively unmask later (e.g. for non-sensitive search boxes).
Set "Mask all text by default" to ON if you handle health, financial, or B2B confidential data. This replaces visible text with asterisks. Aggressive — but safe.
In your DOM, add `class="ph-no-capture"` to any element with sensitive data (credit card forms, password fields, SSN inputs). PostHog skips capture entirely.
For partial visibility, use `class="ph-mask"` — captures element existence but masks content.
For unmasking specific safe inputs (e.g. a non-sensitive search box), use `class="ph-no-mask"`.
Step 2
In `posthog.init`, set `disable_session_recording: false` and `session_recording: { maskAllInputs: true }`. Or toggle on per-user via API.
In your `posthog.init` call: `session_recording: { maskAllInputs: true, maskTextSelector: "*", inlineStylesheet: true, recordCrossOriginIframes: false }`.
`maskTextSelector: "*"` masks all text (use only for high-sensitivity apps). For most products, mask specific selectors: `maskTextSelector: ".user-data, .private"`.
`inlineStylesheet: true` ensures the recording renders with the right CSS even after your site CSS changes.
`recordCrossOriginIframes: false` — never record cross-origin iframes; you do not control their content and they often contain PII (Stripe, Auth0 widgets).
For selective recording (don't record every user): `session_recording: { ..., sampleRate: 0.1 }` records 10% of sessions. Useful for cost control on high-traffic sites.
Step 3
Recording minutes are billed separately from events. 5K free per month, then ~$0.005/min. Set a billing cap. Use sampling on high-traffic.
In PostHog → Organization Settings → Billing → Session Replays → Set billing limit.
Realistic caps: B2B SaaS with 1,000 DAU and 5-min sessions = 150K minutes/month. Set hard cap at 200K minutes.
For high-traffic consumer apps (10K+ DAU), enable sampling: record only 10-20% of sessions. Tag with a flag for the cohorts you care about (paying users, beta-flag users).
Set retention shorter to control storage cost: Project Settings → Data Management → Session recordings retention → 30 days (default) or 7 days for non-critical apps.
For dollar-sensitive apps: record only sessions of specific user cohorts using `sampleRate` + `linked_flag`: `session_recording: { linked_flag: "record_this_user" }` — only records when the flag is true for the user.
Step 4
In PostHog → Session Replay, filter by user property, event, or feature flag. Watch sessions for users who hit your conversion funnel, errored out, or signed up.
Open PostHog → Session Replay.
Filter: "User performed event: signup_completed in last 7 days" — watch new-user sessions to find onboarding friction.
Filter: "User performed event: $exception" — link to Error Tracking issues to watch sessions where users hit errors (PostHog auto-links Sentry / PostHog errors to recordings).
Filter: "Has feature flag: new_checkout_flow=true" + "Did NOT perform: checkout_completed" — watch sessions where the new flow failed to convert.
Save filters as Playlists for recurring review. Most teams have: Onboarding Friction, Errors This Week, High-Value Drop-offs.
Step 5
Mobile replay (iOS / Android / React Native) captures screenshots of the UI, not DOM. Lower fidelity, different masking semantics, more expensive.
Mobile replay is in beta in 2026. It captures periodic screenshots of the app's UI, with overlays for taps and scrolls.
Masking on mobile: tag sensitive views with `ph-no-capture` (iOS / Android) or `<View ph="no-capture">` (React Native). Whole views are blacked out.
Mobile minutes are billed at the same rate as web (~$0.005/min after free tier) but a single mobile minute generates more storage (screenshots > diffs).
Use sampling aggressively on mobile: 5-10% sample is usually enough to find UX issues without exploding costs.
Mobile replay is less useful than web for debugging — you cannot see input values or hover states. Use it for tap-flow analysis, not for form-field debugging.
Step 6
Walk through your app in incognito. Submit a fake CC number, a fake SSN, a fake password. Open the recording in PostHog. Verify all sensitive data is masked.
In incognito, open your app. Walk through 3 flows: signup (with email + password), checkout (with fake CC number — Stripe test card 4242 4242 4242 4242), profile edit (with phone + address).
Open PostHog → Session Replay. Find your test session (filter by your incognito anonymous ID).
Watch the recording at 2x speed. At every input form, verify the value is `***` or blanked. Verify the CC number does NOT appear anywhere.
If you see a real value somewhere, find the form in your codebase, add `ph-no-capture` or `data-ph-no-capture`, redeploy, retest.
Repeat the test quarterly. Frontend changes can re-introduce PII into recordings without anyone noticing.
Common mistakes
Enabling replay before configuring masking
What goes wrong: You flip on session replay at noon. By 6pm, 200 users have used the checkout form. The replays now contain 200 plaintext credit card numbers. PCI scope just expanded — your next audit costs $8,000-15,000 to remediate. (Real incident at a Series A in 2025.)
How to avoid: Configure masking FIRST (Project Settings → Session Replay → Privacy → mask all inputs + text). Validate with a test recording. ONLY THEN enable replay in the SDK config.
Recording every session = $2,000+/mo surprise
What goes wrong: A B2C app launches with 100% replay enabled. 30K users, 6-min average sessions. End of month bill: 180K recording-minutes × $0.005 = $900 on a previously-free PostHog account.
How to avoid: Enable sampling (`sampleRate: 0.1` to 0.2) on high-traffic apps. Or use `linked_flag` to record only specific user cohorts (paying users, error-experiencing users, new signups).
Forgetting iframe content
What goes wrong: Your app embeds a Stripe Checkout iframe. Default replay config captures iframe content. Stripe iframes contain credit-card inputs. You now have CC data in PostHog despite Stripe never storing it.
How to avoid: `recordCrossOriginIframes: false` in session_recording config. Stripe / Auth0 / Plaid widgets are cross-origin and should never be recorded.
Long retention with no reason
What goes wrong: You set retention to 1 year because "longer = better." Your storage cost balloons 10x. You never actually watch a recording older than 14 days — older sessions are useless for product decisions.
How to avoid: 30 days is the right default for most teams. 7 days for high-traffic consumer. 1 year only if you have a specific compliance reason. Adjust in Project Settings → Data Management → Session recordings retention.
No GDPR / DSAR workflow
What goes wrong: EU user requests deletion under GDPR Article 17 (right to erasure). You delete their `Person` in PostHog. Their session recordings are still there for 30 days (the recordings are tied to distinct_id, not Person, and persist independently). You are now non-compliant.
How to avoid: Build a DSAR workflow: on deletion, call PostHog's `/api/projects/{id}/session_recordings/{id}` DELETE endpoint for every recording tied to the user's distinct_id. Document the process in your privacy policy.
Watching recordings without context
What goes wrong: A PM watches 50 random recordings and concludes 'users are confused by the checkout' — but the recordings were all from users who errored out (selection bias). Product team rebuilds checkout based on a non-representative sample. Real users see no improvement.
How to avoid: Filter recordings by funnel position (Insights → Funnel → click "View recordings" on a step). Watch a mix of: converted users, dropped users, errored users. Always note the segment.
Recap
Done — what's next
How to install PostHog tracking across web, mobile, and server
Read the next tutorial
Hand it off
Session replay is the highest-leverage PostHog feature for product teams — and the highest-risk if misconfigured. Most DIY setups either over-mask (replay is useless) or under-mask (PII leakage). A specialist sets up the right balance for your specific data exposure in one session. EverestX matches you with a vetted PostHog specialist in 48 hours, from $14-16/hr.
See specialist rates
Not without config. Default PostHog replay captures form inputs (which are PII) and IP addresses. To be GDPR-compliant: enable mask-all-inputs, mask-all-text for sensitive apps, set `respect_dnt: true`, disable IP storage with `ip: false`, document the basis (legitimate interest or consent) in your privacy policy, and have a DSAR deletion workflow.
On internal users' identify call, set a person property: `posthog.identify(userId, { is_internal: true })`. Then in Project Settings → Session Replay → Recording rules → add a rule: 'Do not record when person.is_internal = true'.
Yes — PostHog session recordings can be downloaded as a JSON dump (DOM events) or rendered video via the export API. For legal hold, export the affected sessions and store in a separate evidence locker. Document the chain of custody.
PostHog: included in product analytics (free 5K min/month, then ~$0.005/min). FullStory: separate product, ~$300-1000/mo flat. Hotjar: heatmap-focused, weaker session-replay UX. PostHog wins on cost for low-mid volume and on integration with the rest of the product-analytics stack.
PostHog needs to initialize and start the recording. The first 1-2 seconds are typically dropped. Workaround: defer the page load slightly (skeleton state) so PostHog has time to start. Or accept the small gap — it's almost never the part of the session that matters.
PostHog
PostHog has a one-line install — and a hundred ways to get it wrong. This walks through web, Next.js App Router, React Native, and the server SDK (which you need for any event that can't be lost). With the autocapture gotchas that show up at month two.
PostHog
Most teams ship 50 events in week one, then spend month four rewriting them because the names made no sense in retrospect. This walks through an event taxonomy that scales, a property schema that does not drift, and the identify flow that keeps your funnel reports honest.
PostHog
Running tests is easy. Running tests that produce real decisions is hard. This walks through hypothesis design, sample-size calculation, the PostHog experiment UI, and the 5 statistical mistakes that invalidate 80% of DIY A/B tests.
PostHog
DIY PostHog is the right call up to a point. Then it isn't. This is the honest framework: when the cost of self-managing exceeds the cost of hiring, and how to tell which side you're on.