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 iOS accessibilityLabel and the Android contentDescription. Set this on any RN component using the standard accessibilityLabel prop. Use this as your primary matching strategy.
  • testID — maps to the iOS accessibilityIdentifier and the Android resource ID. Not exposed to end users or read by VoiceOver or TalkBack. Prefer this over accessibilityLabel when the element doesn't already have a label set for accessibility reasons — adding a testID for 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

  1. Re-run pod install on iOS and trigger a Gradle sync on Android.
  2. Build and run the app.
  3. Enter sample text into a masked field.
  4. Find the session in Connect under Behavior studio > Sessions > Session replay.
  5. Confirm that the input appears as xxxxx in the captured data.

Masking properties reference

The Masking object inside GlobalScreenSettings or a per-screen key accepts the following properties.

PropertyTypeDescription
HasMaskingBooleanSet to true to enable masking. Required.
HasCustomMaskBooleanSet to true to use custom mask characters defined in Sensitive. If false or omitted, masked content is replaced with a fixed xxxxx string.
SensitiveObjectCustom replacement characters. Used only when HasCustomMask is true. See Custom mask characters.
MaskAccessibilityLabelListArray of stringsAccessibility labels of elements to mask. Matches the RN accessibilityLabel prop.
MaskAccessibilityIdListArray of stringsAccessibility identifiers to mask. Matches the RN testID prop.
MaskIdListArray of stringsRegular expressions matching SDK-generated layout paths or Android resource IDs. Use only for elements with no accessibilityLabel or testID.
MaskValueListArray of stringsRegular expressions matching displayed text regardless of which element shows it.
⚠️

Warning

MaskValueList matches any text on screen regardless of element, which can cause performance issues on screens with many elements. Use MaskAccessibilityLabelList or MaskAccessibilityIdList instead 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

ScreenChange controls navigation tracking only — whether the SDK logs that the user visited this screen. Setting ScreenChange: false alone does not prevent content from being captured.


Known limitations

  • Webviews are not captured. Content rendered inside a WebView component 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 accessibilityLabel and testID values 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.