Features
- 📱 Cross-Platform Support - Unified API for iOS and Android
- 🎥 Session Recording - Capture user interactions and screen recordings
- 🔒 Privacy-First - Built-in data masking for sensitive information
- ⚡ High Performance - Native implementation with minimal JavaScript bridge overhead
- 🎯 Selective Recording - Configurable sampling rates and recording controls
- 🚀 New Architecture Ready - Built as a Turbo Module with full type safety
- 🛡️ Granular Privacy Controls - Auto-masking and manual masking via wrapper components
Requirements
- React Native >= 0.70
- iOS >= 13.0
- Android API Level >= 21
- New Architecture support (backward compatible with old architecture)
Installation
Platform Setup
iOS
The SDK dependencies are automatically added via CocoaPods. Your project must target iOS 13 or later.Android
Dependencies are automatically added through Gradle. Requirements:- Minimum Android SDK 21+
- Kotlin support enabled
Quick Start
Here’s a minimal example to get started with Session Replay:Data Residency
Available in React Native Session Replay SDK version
1.3.0 and later.serverURL on MPSessionReplayConfig. The SDK exposes a MPDataResidency constant set with the managed region URLs so you don’t have to hardcode them.
Example Usage
Configuration
TheMPSessionReplayConfig class provides comprehensive control over session replay behavior:
Configuration Options
Auto-Masked View Types
TheMPSessionReplayMask enum defines view types that can be automatically masked:
API Reference
initialize
Initialize the Session Replay SDK with your configuration.token(required) - Your Mixpanel project tokendistinctId(required) - User identifier for the sessionconfig(required) - Session replay configuration
- Token must be a non-empty string
- distinctId must be a non-empty string
- recordingSessionsPercent must be between 0 and 100
startRecording
Start recording user interactions.stopRecording
Stop recording user interactions.isRecording
Check if session recording is currently active.identify
Update the user identifier for the current recording session.distinctId(required) - New user identifier
Remote Configuration
Available in React Native Session Replay SDK version
1.2.0 and later. Requires a paid Session Replay add-on.remote_settings_mode for your project in Mixpanel under Settings > Organization Settings > Session Replay. Using this, you can quickly set SDK options remotely without needing to update your app. This allows you to adjust recording settings, such as sampling rates, on the fly based on your needs.
Three modes are available:
Disabled: Do not use remote configuration and proceed to use the hardcoded initial options provided by the user during initialization. This is the default behavior.Fallback: Attempt to retrieve remote configuration and proceed with those settings. If there is failure or timeout (500 ms), we will use the previously cached remote settings (if one exists from the last successful fetch). If no existing remote settings are cached, we will use the values from the SDK initialization config.Strict: Requires successful remote configuration fetch for SDK initialization. If there is failure or timeout (500 ms), SDK initialization will fail and the Session Replay features will be unavailable for that app launch.
recordingSessionsPercent
Privacy & Data Masking
Session Replay provides two approaches to protect sensitive data: automatic masking and manual masking.Automatic Masking
Configure which view types are automatically masked during initialization:Manual Masking with MPSessionReplayView
Use theMPSessionReplayView wrapper component for granular control over what gets masked:
Complete Masking Example
Wireframes (Beta)
Beta. Wireframes are in beta. Before shipping to production, inspect the wireframes your app produces with
debugOptions.wireframeEmitter and confirm that no sensitive information is captured.Enable wireframe capture
SetwireframesOptions on MPSessionReplayConfig.
MPWireframesOptions takes two options:
How masking applies to wireframe text
Wireframe text passes through four layers, in order. Each layer only sees what survived the one before it. An element is never removed by masking — its role and bounds always ship, so the shape of the screen is preserved. Only its text is affected.
The short version: the SDK does its best to keep the wireframe consistent with what the replay video shows, plus two escape hatches you control — one to add a safe description back (Layer 3), one to catch sensitive content by pattern (Layer 4).
Declare text for an element
wireframeText lets you supply the text for an element yourself — useful for custom components, canvas-drawn content, and masked screens you still want named. Use it to say what a screen is for — "Checkout summary", "Card number" — without revealing what is on it.
wireframeText prop is independent of sensitive, which controls pixels. Set both when you want an element masked in the video but named in the wireframe.
Declared text is sent verbatim, even on a masked element, so never interpolate sensitive user data into it.
Redact by pattern
Some text is sensitive because of what it contains rather than which view it came from — an account number inside a custom view the SDK has no way to recognize, for example.sensitiveRules match on the text itself and run last, after every other masking layer.
There are two behaviors — redact and strip — each available as a literal or a regex match. Redact allows you to replace sensitive text; strip omits all text from the element.
i, m, and s regex flags carry over to native; others are ignored with a warning. A pattern the native platform cannot compile causes initialize to reject rather than silently redacting nothing.
Treat content rules as a backstop, not a boundary. A regex only catches what it matches, on the text the SDK already read. View-level masking is the stronger control; reach for rules when there is no view to mask.
Verify what you are sending
debugOptions.wireframeEmitter hands you each wireframe as it is captured, with a maskDecision per element explaining why its text was kept, rewritten, or dropped.
A few things worth knowing:
- The emitter observes; it does not enable. Setting it without
wireframesOptionscaptures nothing. - Use it in debug builds only. The emitter runs wherever you configure it, so leave it out of the release builds you ship.
- The snapshot shape is not a stable contract. It is for interactive debugging.
Risks to mitigate
Layers 1 and 2 remove anything the video masked. They do not cover every way content can be invisible to the person looking at the screen. These are the specific gaps to check for in your own app before you enable wireframes in production.Text behind an opaque view is still read
Text behind an opaque view is still read
Layer 2 strips text under regions the screenshot masked, not under an ordinary opaque view you placed on top. So when a splash view, a bottom sheet, or a loading scrim covers content, the wireframe may describe text the screenshot does not show.Usually that is fine — content you never marked sensitive is content you were happy to capture either way. It only matters if something sensitive is sitting behind the cover, in which case mask it or give it a
wireframeText.Accessibility labels can describe more than the screen shows
Accessibility labels can describe more than the screen shows
An element with no text of its own can fall back to its accessibility label (
accessibilityLabel / accessible props, resolved by the native SDKs). That is what lets an icon-only button ship as button:Search instead of an anonymous shell.The risk is that a label is not drawn on screen, so — unlike visible text — you cannot confirm what it holds by watching the replay. An avatar whose label is the user’s full name ships that name with no visual cue that there was anything there to mask.Because of that, useAccessibilityLabelFallback is off by default. Turn it on only after auditing what your labels contain, and verify with the debug emitter — label-derived text is reported like any other text, with its own maskDecision.Text the user cannot fully see
Text the user cannot fully see
The SDK reads the string a view was handed, which is not always the string a user can see on-screen. Ellipsized labels, lines clipped by their container, and values scrolled out of frame all report their full value. Element text is capped at 50 characters, which bounds but does not eliminate this.Mitigate: mask or declare text for anything whose full value is more sensitive than its rendered form.
Declared text bypasses view masking by design
Declared text bypasses view masking by design
wireframeText is exempt from Layers 1 and 2 — that is the whole point of it. Content rules still run over it, but nothing else does.Mitigate: treat it as a static string you wrote at build time. Never interpolate sensitive user data into it.Content the SDK cannot classify is not auto-masked
Content the SDK cannot classify is not auto-masked
Automatic masking works by native view type. Custom components that draw their own content are not text components, so
autoMaskedViews does not cover them — the same as for screenshots today, but now they can also carry text.Mitigate: mark custom components sensitive explicitly, and confirm coverage with the debug emitter rather than assuming.Plain <Text> on the legacy architecture, on iOS
Plain <Text> on the legacy architecture, on iOS
On the legacy (Paper) architecture on iOS, a plain
<Text> publishes its string only through an accessibility label, so it needs useAccessibilityLabelFallback — or wireframeText — to carry text in the wireframe. Roles and bounds are unaffected either way.That means turning the fallback on to get readable text also opts every other label-derived string in, so audit them together. Fabric, the default since React Native 0.76, reads text exactly and needs neither. Android is unaffected on both architectures.