Skip to main content
Configure automatic screen and interaction capture for React Native applications using the Userpilot React Native SDK. The Userpilot React Native SDK can automatically capture screen views and user interactions, enabling analytics and engagement without manual screen and event tracking. Screen capture runs through a navigation adapter; interaction capture requires the Userpilot Babel plugin for build-time instrumentation.

SDK Configuration

Auto capture is off by default. Auto capture behavior is configured through options passed to Userpilot.setup during SDK initialization.
Example: disable all text and accessibility label capture for maximum privacy:
Because accessibility labels can mirror on-screen text, set enableInteractionAccessibilityLabelCapture: false whenever you set enableInteractionTextCapture: false. enableInteractionCustomPropsCapture applies only to extra React props copied into the event payload. First-class auto capture fields such as target_class, target_view_name, target_text, target_action, hierarchy, accessibility_label, and accessibility_identifier are controlled by their dedicated options above and are not considered custom props. If a custom prop serializes to a reserved payload key, for example target_action or targetAction, the custom prop is dropped and the SDK-owned value is kept. Eligible names are normalized to their payload form, sorted with a locale-independent alphabetical comparison, and limited to the first five distinct names. The five names reserve their positions before values are validated, so a temporarily invalid or undefined value does not allow a sixth name to replace it.
NoteAuto Capture automatically tracks screen views and ignores manually sent screen events while enabled.If you are migrating from manual screen tracking, please review the migration guide here.

Build-Time Instrumentation

Interaction auto capture also needs build-time instrumentation in the app. Without the Babel plugin, interaction events are not emitted — the SDK does not monkey-patch React Native at runtime. Install the plugin:
Add it to your app’s babel.config.js:
Then reset the Metro cache once:
The plugin wraps interactive React Native JSX (Pressable, Touchable*, Button, TextInput, Switch, Modal) at compile time. The original component is rendered untouched, and refs and props pass through unchanged. Screen capture works without the Babel plugin. This step is required only for interaction capture (enableInteractionAutoCapture: true).

Add third-party libraries

To instrument components from third-party UI libraries (e.g. react-native-paper), pass them as targets in babel.config.js:

Screen Capture

When enableScreenAutoCapture is true and a navigation adapter is configured, the SDK emits a screen event whenever the active route changes. The event sent to the native bridge contains the screen name (the current route name from your navigation library). Deduplication is keyed on the route key (not the screen name), so navigating from /profile/1 to /profile/10 correctly produces two screen events. Pick the adapter matching your navigation library. The SDK uses lazy require(), so customers who haven’t installed a given navigation library never pull it into their bundle. The adapter is selected via NavigationLibraryType:
Manual screensUserpilot.screen('<title>') is ignored whenever enableScreenAutoCapture is true, including when navigation is missing. To send screens manually, leave enableScreenAutoCapture: false and call Userpilot.screen(...) yourself.

React Navigation

Pass the NavigationContainer ref as navigation. The SDK listens to route state changes and uses the current route name as the screen name.

React Native Navigation (Wix)

For react-native-navigation, set the library and register your RNN screens as usual. No navigation handle is needed because the SDK listens to Navigation.events().registerComponentDidAppearListener(...) globally.

Expo Router

Expo Router does not expose a NavigationContainer ref. Set the library and mount <UserpilotExpoRouterTracker /> once in your root app/_layout.tsx. The SDK uses the exact usePathname() value as the screen name, so paths such as /profile/1 and /profile/10 are captured as different screen events.

Missing navigation configuration

Screen auto capture never falls back to manual screen names. If enableScreenAutoCapture is true but navigation is omitted, the SDK logs this warning and does not capture screen events:
Calls to Userpilot.screen('Profile') are ignored whenever screen auto capture is enabled, including when the navigation configuration is missing. This prevents manual names from silently becoming the source for an automatic screen stream. If you want to track screens manually, leave screen auto capture disabled and call Userpilot.screen(...) as usual:
When interaction auto capture is enabled, these manual calls also keep the current-screen context for later interaction events.

Interaction Capture

When enableInteractionAutoCapture: true, the SDK emits an event for the following user interactions (requires the Babel plugin): Each interaction maps to a high-level analytics category (tap, text_change, selection_change, value_change, view_presented) — that is the event name that reaches your analytics backend.

Component Value Gating

By default, user-input values are stripped from event payloads — toggle states, selected values, and text length metadata do not leave the device. To include them, pass enableInteractionValueCapture: true in Userpilot.setup. Position and configuration fields (selected_index) are always captured because they don’t reveal user input.

Privacy Controls

Event Metadata Keys

Common fields on interaction events:

hierarchy Format

hierarchy is a denormalized leaf-to-root string. Each semicolon-delimited segment looks like: ComponentName:attr__id="…",attr__index="N",attr__desc="…"
  • attr__desc — only the leaf segment’s accessibility_label. Omitted when enableInteractionAccessibilityLabelCapture: false, under <UserpilotIgnoreInteractions>, or when no label exists. Ancestor segments deliberately omit it.
  • attr__id — leaf: the accessibility_identifier (testID), falling back to accessibility_label. Ancestors: any stamped accessibility id from JSX ancestry.
  • attr__index — zero-based sibling index under the same parent.
Segments are joined with ;. Under <UserpilotRedactText>, hierarchy is preserved; visible text fields are replaced with **** rather than dropped from the path attributes controlled by accessibility capture.

UserpilotRedactText

Wraps a subtree so auto capture replaces every text label it would otherwise capture (target_text, dialog_title, dialog_message, etc.) with ****. The interaction event still fires — the component is still identified by type, position, and structure — only the human-readable text is masked. Capture configuration is applied before redacted values leave the SDK. If enableInteractionTextCapture: false, text fields such as target_text are removed from the payload instead of being sent as ****. If enableInteractionAccessibilityLabelCapture: false, accessibility fields are removed from the payload and from hierarchy attributes.
hierarchy remains on redacted events. Remove the wrapper to disable masking for that subtree.

UserpilotIgnoreInteractions

Wraps a subtree so auto capture skips interaction events whose hit-tested component lies inside it (taps, value changes, text-field updates tied to that field, etc.). Route-driven screen events are not suppressed by UserpilotIgnoreInteractions — they come from the navigation adapter, not from component hit testing. The host app’s own gesture handlers (onPress, …) still run — only auto capture ignores those interactions.
Use UserpilotIgnoreInteractions for interactions whose mere occurrence would leak intent (e.g. tapping a row in a contacts list, where row position correlates 1:1 with contact identity). Both wrappers are passive markers — they render children unchanged. Wrap the outermost component you want affected; descendants of the wrapper are covered, ancestors are not. withUserpilotIgnoreInteractions(Component) returns a wrapped component that is always ignored. Use it when you want a single, named, ignored version of a component:

Runtime Controls

To stop or resume collection without changing options — for example a “private mode” — use:
Both calls are idempotent. They preserve the options supplied to Userpilot.setup(...). While stopped, no screen or interaction events are recorded regardless of other configuration. This is a global toggle that overrides per-subtree settings.

Limitations

  • Babel plugin required for interactions — without @userpilot/babel-plugin-react-native-autocapture, interaction events are not emitted. Screen capture does not require the plugin.
  • navigation required for screen auto capture — if enableScreenAutoCapture is true but navigation is omitted, no screen events are captured. Manual Userpilot.screen(...) calls are also ignored while screen auto capture is enabled.
  • Third-party UI libraries — components outside the built-in React Native set are not instrumented unless listed in the Babel plugin’s targets option.
  • Expo Router tracker<UserpilotExpoRouterTracker /> must be mounted once in the root layout; omitting it means no automatic screen events for Expo Router.