Mask PII in session replay (React Native)
When capturing user behavior data, you must take measures to exclude personally identifiable information (PII) — such as credit card numbers, home addresses, or names — before that content leaves the device. To do this, assign accessibility identifiers to sensitive UI elements, then declare masking rules in a configuration file. In React Native, both steps use the SDK's cross-platform config — ConnectConfig.json — rather than a separate layout config file.
Language: TypeScript and JavaScript
Availability: Ultimate. Masking applies to session replay captures, which are not produced on Pro or Premium subscriptions.
Before you begin
The SDK identifies UI elements for masking by matching against one of three properties set on each element:
accessibilityLabel— maps to the iOSaccessibilityLabeland the AndroidcontentDescription. Set this on any RN component using the standardaccessibilityLabelprop. Use this as your primary matching strategy.testID— maps to the iOSaccessibilityIdentifierand the Android resource ID. Not exposed to end users or read by VoiceOver or TalkBack. Prefer this overaccessibilityLabelwhen the element doesn't already have a label set for accessibility reasons — adding atestIDfor masking purposes won't affect assistive technology behavior.- Layout path — an XPath-style string generated by the SDK when an element has neither of the above. Layout paths are positional and can change as your component tree changes; match them using a regular expression and treat them as a last resort.
Assign accessibilityLabel or testID to every component that displays PII. In most cases you can reuse labels you have already set for accessibility purposes, which keeps your masking rules aligned with your VoiceOver / TalkBack configuration.
Configure masking
Masking rules live inside ConnectConfig.json, under a single layoutConfig key. The AutoLayout object inside it is applied identically to both platforms — there's no way to set different masking rules for iOS and Android through this file.
Step 1: Label sensitive components
Add accessibilityLabel or testID to any component whose content must be masked. You can use either prop or both; the config in Step 2 references the value you choose.
// Text input containing a card number
<TextInput
accessibilityLabel="Card Number"
placeholder="Card number"
keyboardType="numeric"
/>
// Displaying a user's full name
<Text accessibilityLabel="Full Name">USER.FULLNAME</Text>// Text input containing a card number
<TextInput
testID="card-number-input"
placeholder="Card number"
keyboardType="numeric"
/>
// Displaying a user's full name
<Text testID="full-name-display">USER.FULLNAME</Text>Reference accessibilityLabel values in MaskAccessibilityLabelList and testID values in MaskAccessibilityIdList in your config. See Masking properties reference.
Step 2: Add masking rules to ConnectConfig.json
Open ConnectConfig.json and add a layoutConfig key inside the Connect object. It contains an AutoLayout object with masking rules.
Use GlobalScreenSettings to mask the same elements across your entire app.
{
"Connect": {
"AppKey": "YOUR_CONNECT_APP_KEY_HERE",
"PostMessageUrl": "YOUR_POST_MESSAGE_URL_HERE",
"KillSwitchUrl": "YOUR_KILL_SWITCH_URL_HERE",
"useRelease": false,
"layoutConfig": {
"AutoLayout": {
"GlobalScreenSettings": {
"Masking": {
"HasMasking": true,
"MaskAccessibilityLabelList": [
"Card Number",
"Full Name",
"Billing Address"
]
}
}
}
}
}
}Step 3: Rebuild and verify
- Re-run
pod installon iOS and trigger a Gradle sync on Android. - Build and run the app.
- Enter sample text into a masked field.
- Find the session in Connect under Behavior studio > Sessions > Session replay.
- Confirm that the input appears as
xxxxxin the captured data.
Masking properties reference
The Masking object inside GlobalScreenSettings or a per-screen key accepts the following properties.
| Property | Type | Description |
|---|---|---|
HasMasking | Boolean | Set to true to enable masking. Required. |
HasCustomMask | Boolean | Set to true to use custom mask characters defined in Sensitive. If false or omitted, masked content is replaced with a fixed xxxxx string. |
Sensitive | Object | Custom replacement characters. Used only when HasCustomMask is true. See Custom mask characters. |
MaskAccessibilityLabelList | Array of strings | Accessibility labels of elements to mask. Matches the RN accessibilityLabel prop. |
MaskAccessibilityIdList | Array of strings | Accessibility identifiers to mask. Matches the RN testID prop. |
MaskIdList | Array of strings | Regular expressions matching SDK-generated layout paths or Android resource IDs. Use only for elements with no accessibilityLabel or testID. |
MaskValueList | Array of strings | Regular expressions matching displayed text regardless of which element shows it. |
Warning
MaskValueListmatches any text on screen regardless of element, which can cause performance issues on screens with many elements. UseMaskAccessibilityLabelListorMaskAccessibilityIdListinstead wherever possible.
Custom mask characters
When HasCustomMask is true, the Sensitive object controls replacement characters by character class.
"Masking": {
"HasMasking": true,
"HasCustomMask": true,
"Sensitive": {
"capitalCaseAlphabet": "X",
"smallCaseAlphabet": "x",
"number": "9",
"symbol": "#"
},
"MaskAccessibilityLabelList": ["Card Number"]
}With this config, a value like Card4111 is captured as Xxxx9999.
Matching layout paths with regular expressions
When an element has no accessibilityLabel or testID, the SDK assigns it a layout path that encodes its position in the view hierarchy — for example, [w,0],[v,1],[v,1],[b,9]. To reference such elements in MaskIdList, write a regular expression rather than a literal path, since the path can change as the component tree changes around it.
For example, ^9[0-9][0-9][0-9]$ matches any element whose path is a four-digit number in the 9000s. You can test patterns at Rubular before adding them to the config.
Setting accessibilityLabel or testID on the element is always preferable — it is more stable and more readable.
Disable capture for an entire screen
To prevent any part of a screen from being recorded, set CaptureUserEvents and ScreenShot to false for that screen.
"layoutConfig": {
"AutoLayout": {
"GlobalScreenSettings": {
"ScreenChange": true,
"Masking": {
"HasMasking": true,
"MaskAccessibilityLabelList": ["Full Name"]
}
},
"SensitiveDocumentScreen": {
"CaptureUserEvents": false,
"ScreenShot": false
}
}
}CaptureUserEvents: false stops interaction and gesture capture. ScreenShot: false disables screenshot capture for all events on that screen. Together, they effectively exclude the screen from session replay.
Note
ScreenChangecontrols navigation tracking only — whether the SDK logs that the user visited this screen. SettingScreenChange: falsealone does not prevent content from being captured.
Known limitations
- Webviews are not captured. Content rendered inside a
WebViewcomponent is not recorded in session replay. The replay shows a blank frame for the duration the webview is visible. This includes inline webviews and full-screen webviews used for OAuth or SSO flows. Masking rules have no effect inside a webview — there is nothing to mask. - Right-to-left layouts. Masking coordinate calculations are left-to-right anchored. In RTL locales, mask regions may not align correctly with the underlying UI elements, leaving PII exposed or masking the wrong area. If your app supports RTL languages, verify masking manually by inspecting session replays from a device set to an RTL locale before releasing.
- Dynamic content. Masking rules match against
accessibilityLabelandtestIDvalues at capture time. If your app updates these props dynamically — for example, swapping a masked field in and out of the component tree — the masking rule may not apply during the transition frame. Prefer stable, static labels on PII fields.
