React Native SDK
Add session replay, crash reporting, and network monitoring to your React Native app in under 5 minutes. Works with Expo managed workflow, Expo bare, and plain React Native. No native module linking required for Expo.
On this page
Installation
Install the BugsPulse React Native package from npm. No additional native setup is required for Expo managed projects.
npm install @bugspulse/react-native
# or
yarn add @bugspulse/react-native
# or
pnpm add @bugspulse/react-nativeExpo managed workflow
No additional steps needed. The package uses Expo-compatible APIs and works out of the box.
Bare React Native
For bare React Native projects (not Expo), run pod install after npm install:
cd ios && pod installInitialization
Call BugsPulse.init() as early as possible — before registerRootComponent or the root render call. The earlier you initialize, the more complete your session data will be.
import { AppRegistry } from 'react-native';
import BugsPulse from '@bugspulse/react-native';
import App from './App';
BugsPulse.init({
apiKey: 'bp_live_your_key_here', // Required — from project settings
environment: 'production', // 'development' | 'staging' | 'production'
});
AppRegistry.registerComponent('MyApp', () => App);Expo with app.config.js
import Constants from 'expo-constants';
import BugsPulse from '@bugspulse/react-native';
BugsPulse.init({
apiKey: Constants.expoConfig?.extra?.bugspulseApiKey ?? '',
environment: __DEV__ ? 'development' : 'production',
});module.exports = {
extra: {
bugspulseApiKey: process.env.BUGSPULSE_API_KEY,
},
};Configuration options
All options passed to BugsPulse.init():
| Option | Type | Default | Description |
|---|---|---|---|
| apiKey | string | required | Your project API key from the dashboard. |
| apiUrl | string | https://api.bugspulse.com | Override the ingest endpoint. Only needed for local development. |
| environment | string | 'production' | Tag sessions by environment. Shown as a filter in the dashboard. |
| captureNetworkRequests | boolean | true | Intercept fetch, XMLHttpRequest, and Axios to log API calls. |
| captureCrashes | boolean | true | Handle unhandled JS errors and Promise rejections. |
| captureTouches | boolean | true | Record tap, swipe, and scroll events for replay. |
| captureNavigation | boolean | true | Record React Navigation screen transitions. |
| sessionSamplingRate | number | 1.0 | Fraction 0–1 of sessions to capture. 0.5 = 50% of sessions. |
| flushIntervalMs | number | 5000 | How often (ms) to flush buffered events to the API. |
| maxOfflineQueueSize | number | 1000 | Max events to buffer when the device is offline. |
| redactedFields | string[] | [] | Additional field names to redact from network request bodies. |
| debug | boolean | false | Log SDK internals to the console. Useful during integration. |
User identification
Associate sessions with an internal user identifier so you can search for all sessions from a specific user. Never pass email addresses, names, or other PII — use an opaque internal user ID.
import BugsPulse from '@bugspulse/react-native';
// Call after the user signs in
BugsPulse.setUser('user_abc123');
// Add structured context to the session
BugsPulse.setContext({
plan: 'pro',
appVersion: '2.4.1',
featureFlags: { newCheckout: true },
});
// Clear identity when the user signs out
BugsPulse.clearUser();setUser(). Use an opaque internal ID that you can map to a user in your own systems.Custom events
Track business-logic events that matter to you. Custom events appear on the session timeline alongside automatic events, making it easy to correlate user actions with crashes or errors.
import BugsPulse from '@bugspulse/react-native';
// Simple event
BugsPulse.track('checkout_started');
// Event with properties — all properties appear in the replay timeline
BugsPulse.track('checkout_completed', {
plan: 'pro',
amount_usd: 29,
coupon_applied: true,
});
// Add 'screen' to any event to show which screen it happened on
BugsPulse.track('button_press', { screen: 'HomeScreen', buttonId: 'cta' });
BugsPulse.track('product_tap', { screen: 'CatalogScreen', productId: 'sku_123' });Navigation events
Track screen transitions so the session replay can update the Current Screen indicator as the user moves through your app. Use navigate with from / to, or screen_view with screenName:
// Track a screen transition
BugsPulse.track('navigate', { from: 'HomeScreen', to: 'CheckoutScreen' });
// Or use screen_view style
BugsPulse.track('screen_view', { screenName: 'ProductDetailScreen' });checkout_completed over CheckoutScreen.Crash capture
The SDK automatically captures unhandled JS errors and unhandled Promise rejections. You can also capture exceptions manually from try/catch blocks.
import BugsPulse from '@bugspulse/react-native';
// Manual exception capture
async function processPayment(orderId: string) {
try {
await paymentService.charge(orderId);
} catch (error) {
// Capture with context — the context appears in the crash report
BugsPulse.captureException(error as Error, {
orderId,
context: 'payment_processing',
retryCount: 2,
});
// Re-throw if you want the app to handle it too
throw error;
}
}React error boundary
Wrap your root component with an error boundary to catch render errors and report them before showing a fallback UI:
import React from 'react';
import BugsPulse from '@bugspulse/react-native';
class ErrorBoundary extends React.Component<
{ children: React.ReactNode; fallback: React.ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
BugsPulse.captureException(error, {
context: 'react_error_boundary',
componentStack: info.componentStack ?? '',
});
}
render() {
return this.state.hasError ? this.props.fallback : this.props.children;
}
}
export default ErrorBoundary;Network monitoring
When captureNetworkRequests: true (the default), the SDK automatically intercepts fetch and XMLHttpRequest calls. Each request is logged with URL, method, status, duration, and request/response size.
captureRequestBody option — bodies are automatically scrubbed of sensitive fields.Ignore specific domains
BugsPulse.init({
apiKey: 'bp_live_...',
networkIgnorePatterns: [
/analytics.example.com/, // Regex pattern
'cdn.example.com', // Exact domain string
],
});Axios interceptor
If you use Axios, attach the BugsPulse interceptor for richer metadata:
import axios from 'axios';
import { createAxiosInterceptor } from '@bugspulse/react-native';
const api = axios.create({ baseURL: 'https://api.example.com' });
createAxiosInterceptor(api);
export default api;Session control
Sessions are started automatically on init. Call BugsPulse.endSession() when the app moves to the background to record the session duration and mark it as completed.
import { AppState, type AppStateStatus } from 'react-native';
import { useEffect, useRef } from 'react';
import BugsPulse from '@bugspulse/react-native';
export default function App() {
const appState = useRef(AppState.currentState);
useEffect(() => {
const sub = AppState.addEventListener('change', (next: AppStateStatus) => {
if (appState.current === 'active' && next.match(/background|inactive/)) {
BugsPulse.endSession();
}
if (appState.current.match(/background|inactive/) && next === 'active') {
BugsPulse.startSession(); // Start a new session on foreground
}
appState.current = next;
});
return () => sub.remove();
}, []);
return <RootNavigator />;
}endSession(), all sessions will remain in the active state and the Avg Session Duration metric in analytics will show 0.Privacy & redaction
BugsPulse is privacy-first by design. The following are never captured:
- Keyboard input — individual keystrokes are never recorded
- Clipboard contents
- Passwords, tokens, and authorization headers (automatically redacted)
- Request/response bodies (unless explicitly enabled)
- Camera, microphone, or biometric data
- Video or screenshots of the screen
Custom redaction rules
Add field names that should be scrubbed from network payloads:
BugsPulse.init({
apiKey: 'bp_live_...',
redactedFields: [
'ssn',
'creditCard',
'cvv',
'dob',
'socialSecurityNumber',
/secret.*/i, // Regex patterns also supported
],
});Offline support
Events captured while the device has no connectivity are stored in an in-memory queue and flushed automatically when the connection is restored. The queue defaults to maxOfflineQueueSize: 1000 events. When the limit is reached, the oldest events are dropped to make room for new ones. Queued events are not persisted to disk, so they are lost if the app is killed while offline.
Troubleshooting
Sessions are not appearing in the dashboard
Enable debug: true in your init config and check the console for flush errors. Most common cause is an incorrect API key or a network request being blocked by a proxy.
Network requests are not being captured
Make sure captureNetworkRequests is not set to false. If you're using a custom fetch polyfill, pass it to BugsPulse.init({ fetchOverride: myFetch }).
The SDK is causing bundle size to increase significantly
The SDK is under 5KB gzipped. Check for accidental double-imports or peer dependencies being bundled. Run npx react-native bundle --stats and inspect the output.
Crashes are not being captured in Hermes
Hermes crashes require source maps to be uploaded for symbolication. Use the bugspulse upload-sourcemaps CLI command as part of your release build.