> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt # React Native ## Installation Our React Native enables you to integrate PostHog with your React Native project. For React Native projects built with Expo, there are no mobile native dependencies outside of supported Expo packages. To install, add the `posthog-react-native` package to your project as well as the required peer dependencies. #### Expo apps Terminal ```bash npx expo install posthog-react-native expo-file-system expo-application expo-device expo-localization ``` #### React Native apps Terminal ```bash yarn add posthog-react-native @react-native-async-storage/async-storage react-native-device-info react-native-localize # or npm i -s posthog-react-native @react-native-async-storage/async-storage react-native-device-info react-native-localize ``` #### React Native Web and macOS If you're using [React Native Web](https://github.com/necolas/react-native-web) or [React Native macOS](https://github.com/microsoft/react-native-macos), do not use the [expo-file-system](https://github.com/expo/expo/tree/master/packages/expo-file-system) package since the Web and macOS targets aren't supported, use the [@react-native-async-storage/async-storage](https://github.com/react-native-async-storage/async-storage) package instead. ### Configuration #### With the PosthogProvider The recommended way to set up PostHog for React Native is to use the `PostHogProvider`. This utilizes the Context API to pass the PostHog client around, and enables [autocapture](/docs/product-analytics/autocapture.md). To set up `PostHogProvider`, add it to your `App.js` or `App.ts` file: App.js ```jsx // App.(js|ts) import { usePostHog, PostHogProvider } from 'posthog-react-native' ... export function MyApp() { return ( ) } ``` Then you can access PostHog using the `usePostHog()` hook: React Native ```jsx const MyComponent = () => { const posthog = usePostHog() useEffect(() => { posthog.capture("event_name") }, [posthog]) } ``` #### Without the PosthogProvider If you prefer not to use the provider, you can initialize PostHog in its own file and import the instance from there: posthog.ts ```jsx import PostHog from 'posthog-react-native' export const posthog = new PostHog('', { // usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com' host: 'https://us.i.posthog.com' }) ``` Then you can access PostHog by importing your instance: React Native ```jsx import { posthog } from './posthog' export function MyApp1() { useEffect(() => { posthog.capture('event_name') }, []) return Your app code } ``` You can even use this instance with the PostHogProvider: React Native ```jsx import { posthog } from './posthog' export function MyApp() { return {/* Your app code */} } ``` ### Choose an iOS dependency path for the native plugin The optional `@posthog/react-native-plugin` package adds native features such as session replay and native crash capture. Install it as described in the guide for the feature that you use. Then choose one iOS dependency path: | Path | Requirements | What it resolves | | --- | --- | --- | | CocoaPods | A React Native project that uses CocoaPods | CocoaPods resolves the plugin and `posthog-ios`. This remains the default path. | | CocoaPods with `posthog-ios` through Swift Package Manager | React Native 0.75 or later and a CocoaPods project with dynamic frameworks | CocoaPods resolves the plugin. Swift Package Manager resolves `posthog-ios`. | | Full Swift Package Manager | Verified with an iOS-only React Native 0.87.1 app and React Native Community CLI 20.2.0. Requires `@posthog/react-native-plugin` 2.4.0 or later, Xcode 16 or later, and an iOS 15.1 or later app deployment target. | React Native's experimental Swift Package Manager integration resolves the plugin and `posthog-ios`. This path does not use CocoaPods. | This verification does not cover Expo or other React Native versions. Use CocoaPods or the hybrid path unless you validate the full Swift Package Manager path for your configuration. #### CocoaPods Use the standard React Native CocoaPods flow: Terminal ```bash cd ios pod install ``` The plugin podspec adds `posthog-ios` as a CocoaPods dependency. You do not need to add `posthog-ios` separately. #### CocoaPods with `posthog-ios` through Swift Package Manager Add the following property to `ios/Podfile.properties.json`: JSON ```json { "posthog.useSpm": "true" } ``` Add dynamic frameworks to your `ios/Podfile`: Ruby ```ruby use_frameworks! :linkage => :dynamic ``` Then install the pods: Terminal ```bash cd ios pod install ``` This setting changes only how the plugin resolves `posthog-ios`. The plugin and other React Native dependencies still use CocoaPods. #### Full Swift Package Manager This path uses React Native's experimental CocoaPods-free iOS integration. Every native dependency in your app must support React Native's full Swift Package Manager integration. Use CocoaPods or the hybrid path if a dependency does not support it. Install your JavaScript dependencies first. Make a clean commit or a backup of your iOS project before the conversion. Then run this command from the `ios` directory: Terminal ```bash npx react-native spm add --deintegrate --yes ``` The `--deintegrate` option removes the complete CocoaPods integration from the iOS project. React Native then finds the plugin's `ios/Package.swift` manifest. Swift Package Manager resolves the plugin and `posthog-ios`. Do not run `pod install` for this path. PostHog CI verifies this path with the configuration in the requirements table. The verified app sets its deployment target to iOS 15.1. The plugin package manifest has a separate iOS 15 minimum. The CocoaPods and hybrid paths keep the plugin podspec's iOS 13 minimum. Set up a reverse proxy (recommended) We recommend [setting up a reverse proxy](/docs/advanced/proxy.md), so that events are less likely to be intercepted by tracking blockers. We have our [own managed reverse proxy service](/docs/advanced/proxy/managed-reverse-proxy.md), which is free for all PostHog Cloud users, routes through our infrastructure, and makes setting up your proxy easy. If you don't want to use our managed service then there are several other options for creating a reverse proxy, including using [Cloudflare](/docs/advanced/proxy/cloudflare.md), [AWS Cloudfront](/docs/advanced/proxy/cloudfront.md), and [Vercel](/docs/advanced/proxy/vercel.md). Grouping products in one project (recommended) If you have multiple customer-facing products (e.g. a marketing website + mobile app + web app), it's best to install PostHog on them all and [group them in one project](/docs/settings/projects.md). This makes it possible to track users across their entire journey (e.g. from visiting your marketing website to signing up for your product), or how they use your product across multiple platforms. Add IPs to Firewall/WAF allowlists (recommended) For certain features like [heatmaps](/docs/toolbar/heatmaps.md), your Web Application Firewall (WAF) may be blocking PostHog's requests to your site. Add these IP addresses to your WAF allowlist or rules to let PostHog access your site. **EU**: `3.75.65.221`, `18.197.246.42`, `3.120.223.253` **US**: `44.205.89.55`, `52.4.194.122`, `44.208.188.173` These are public, stable IPs used by PostHog services. PostHog captures heatmap screenshots using [Browserless](https://www.browserless.io), which has its own IP addresses. Browserless [publishes the current list here](https://docs.browserless.io/baas/troubleshooting/whitelisting-ips). An allowlist does not help when your app has a private address. For apps on an internal network, see [internal and intranet applications](/docs/session-replay/troubleshooting.md#internal-and-intranet-applications). ### Configuration options You can further customize how PostHog works through its configuration on initialization. | Attribute | Description | | --- | --- | | `host` **Type:** String **Default:** `https://us.i.posthog.com` | PostHog API host (usually `https://us.i.posthog.com` by default or `https://eu.i.posthog.com`). Host is optional if you use `https://us.i.posthog.com`. | | `flushAt` **Type:** Number **Default:** `20` | The number of events to queue before sending to PostHog (flushing). | | `flushInterval` **Type:** Number **Default:** `10000` | The interval in milliseconds between periodic flushes. | | `maxBatchSize` **Type:** Number **Default:** `100` | The maximum number of queued messages to be flushed as part of a single batch (must be higher than `flushAt`). | | `maxQueueSize` **Type:** Number **Default:** `1000` | The maximum number of cached messages either in memory or on the local storage (must be higher than `flushAt`). | | `disabled` **Type:** Boolean **Default:** `false` | If set to true, the SDK is essentially disabled (useful for local environments where you don't want to track anything). | | `defaultOptIn` **Type:** Boolean **Default:** `true` | If set to false, the SDK will not track until the `optIn()` function is called. | | `sendFeatureFlagEvent` **Type:** Boolean **Default:** `true` | Whether to track that `getFeatureFlag` was called (used by experiments). | | `preloadFeatureFlags` **Type:** Boolean **Default:** `true` | Whether to load feature flags when initialized or not. | | `bootstrap` **Type:** Object **Default:** `{}` | Seeds identity (`distinctId`, `isIdentifiedId`) and feature flag state (`featureFlags`, `featureFlagPayloads`) during initialization. See [SDK bootstrapping](/docs/libraries/bootstrapping.md). | | `disableRemoteFeatureFlags` **Type:** Boolean **Default:** `false` | When `true`, the SDK never fetches or evaluates feature flags from PostHog, and `identify()`, `group()`, and `reset()` stop triggering `/flags` requests. Supply flag values yourself via `bootstrap` (at startup) and `updateFlags()` (at runtime). Available in version 4.49.0+. | | `fetchRetryCount` **Type:** Number **Default:** `3` | How many times HTTP requests will be retried. | | `fetchRetryDelay` **Type:** Number **Default:** `3000` | The delay between HTTP request retries. | | `requestTimeout` **Type:** Number **Default:** `10000` | Timeout in milliseconds for any calls. | | `featureFlagsRequestTimeoutMs` **Type:** Number **Default:** `10000` | Timeout in milliseconds for feature flag calls. | | `sessionExpirationTimeSeconds` **Type:** Number **Default:** `1800` | For session analysis, how long before a session expires (defaults to 30 minutes). | | `persistence` **Type:** String **Default:** `file` | Allows you to provide the storage type. `file` will try to load the best available storage, the provided `customStorage`, `customAsyncStorage`, or in-memory storage. | | `customAppProperties` **Type:** Object or Function **Default:** `null` | Allows you to provide your own implementation of the common information about your App or a function to modify the default App properties generated. | | `customStorage` **Type:** Object **Default:** `null` | Allows you to provide a custom asynchronous storage such as `async-storage`, `expo-file-system`, or a synchronous storage such as `mmkv`. If not provided, PostHog will attempt to use the best available storage via optional peer dependencies. If `persistence` is set to `memory`, this option is ignored. | | `captureAppLifecycleEvents` **Type:** Boolean **Default:** `true` | Captures app lifecycle events such as Application Installed, Application Updated, Application Opened, Application Became Active, and Application Backgrounded. Enabled by default since version 4.39.0. | | `disableGeoip` **Type:** Boolean **Default:** `false` | When true, disables automatic GeoIP resolution for events and feature flags. | | `enableSessionReplay` **Type:** Boolean **Default:** `false` | Enable Recording of Session replay for Android and iOS. | | `sessionReplayConfig` **Type:** Object **Default:** `null` | Session replay configuration. See the [replay install docs](/docs/session-replay/installation.md) for more details. | | `enablePersistSessionIdAcrossRestart` **Type:** Boolean **Default:** `false` | When true, persists the `$session_id` across app restarts. If false, `$session_id` always resets on app restart. | | `evaluationContexts` **Type:** Array of Strings **Default:** `undefined` | Evaluation context tags that constrain which feature flags are evaluated. When set, only flags with matching evaluation context tags (or no evaluation context tags) will be returned. This helps reduce unnecessary flag evaluations and improves performance. See [evaluation contexts documentation](/docs/feature-flags/evaluation-contexts.md) for more details. Available in version 4.21.0+. The legacy parameter `evaluationEnvironments` (version 4.10.0+) is also supported for backward compatibility. | | `addTracingHeaders` **Type:** Array of Strings **Default:** `undefined` | Hostnames for which PostHog should add tracing headers to outgoing `fetch` requests. Matching requests include `X-POSTHOG-DISTINCT-ID` and `X-POSTHOG-SESSION-ID`, which lets backend events, errors, and LLM traces link back to frontend sessions and replays. Use hostnames only, without the protocol or path. | | `before_send` **Type:** Function **Default:** `undefined` | A callback function that is called before each event is sent to PostHog. You can use it to modify, filter, or suppress events. Return `null` to drop the event, or return the modified event to send it. See [customizing exception capture](#customizing-exception-capture-with-before_send) for details. | | `capturePushNotificationSubscriptions` **Type:** Boolean **Default:** `true` | Whether to automatically register this device's push token so [Workflows](/docs/workflows.md) can target it. Requires `@posthog/react-native-plugin`. See [push notifications](#push-notifications). Available in version 4.62.0+. | | `capturePushNotificationOpened` **Type:** Boolean **Default:** `true` | Whether to automatically capture `$push_notification_opened` when the user taps a push notification. Requires `@posthog/react-native-plugin`. See [push notifications](#push-notifications). Available in version 4.62.0+. | | `pushIdentityProvider` **Type:** Function **Default:** `undefined` | Supplies a signed identity-verification token for push subscription requests. Only needed when your push channel requires identity verification. See [identity verification](#identity-verification). Available in version 4.62.0+. | ### Tracing headers Use `addTracingHeaders` to connect React Native network requests to backend events, errors, and LLM traces captured by a server-side PostHog SDK: typescript ```typescript const posthog = new PostHog('', { host: 'https://us.i.posthog.com', addTracingHeaders: ['api.example.com'], }) ``` Hostnames are matched exactly. The SDK patches global `fetch` and sends `X-POSTHOG-DISTINCT-ID` and `X-POSTHOG-SESSION-ID` on matching requests when those values are available. ## Capturing events You can send custom events using `capture`: React Native ```jsx posthog.capture('user_signed_up') ``` > **Tip:** We recommend using a `[object] [verb]` format for your event names, where `[object]` is the entity that the behavior relates to, and `[verb]` is the behavior itself. For example, `project created`, `user signed up`, or `invite sent`. ### Setting event properties Optionally, you can include additional information with the event by including a [properties](/docs/data/events.md#event-properties) object: React Native ```jsx posthog.capture('user_signed_up', { login_type: "email", is_free_trial: true }) ``` ### Capturing screen views #### With `@react-navigation/native` and autocapture: When using [@react-navigation/native](https://reactnavigation.org/docs/6.x/getting-started) v6 or lower, screen tracking is automatically captured if the [`autocapture`](/docs/libraries/react-native.md#autocapture) property is used in the `PostHogProvider`: It is important that the `PostHogProvider` is configured as a child of the `NavigationContainer`: React Native ```jsx // App.(js|ts) import { PostHogProvider } from 'posthog-react-native' import { NavigationContainer } from '@react-navigation/native' export function App() { return ( {/* Rest of app */} ) } ``` When using [@react-navigation/native](https://reactnavigation.org/docs/7.x/getting-started) v7 or higher, screen tracking has to be manually captured: React Native ```jsx // App.(js|ts) import { PostHogProvider } from 'posthog-react-native' import { NavigationContainer } from '@react-navigation/native' // Using `PostHogProvider` is optional, but needed if you want to capture touch events automatically with the `captureTouches` option. export function App() { return ( {/* Rest of app */} ) } ``` Check out and set it up the official way for [Screen tracking for analytics](https://reactnavigation.org/docs/screen-tracking/). Then call the `screen` method within the `trackScreenView` method. React Native ```jsx const posthog = usePostHog() // use the usePostHog hook if using the PostHogProvider or your own custom posthog instance // you can read the params from `getCurrentRoute()` posthog.screen(currentRouteName, params) ``` #### With `react-native-navigation` and autocapture: First, simplify the wrapping of your screens with a shared PostHogProvider: React Native ```jsx import PostHog, { PostHogProvider } from 'posthog-react-native' import { Navigation } from 'react-native-navigation'; export const posthog = new PostHog(''); export const SharedPostHogProvider = (props: any) => { return ( {props.children} ); }; ``` Then, every screen needs to be wrapped with this provider if you want to capture touches or use the `usePostHog()` hook React Native ```jsx export const MyScreen = () => { return ( ... ); }; Navigation.registerComponent('Screen', () => MyScreen); Navigation.events().registerAppLaunchedListener(async () => { posthog.initReactNativeNavigation({ navigation: { // (Optional) Set the name based on the route. Defaults to the route name. routeToName: (name, properties) => name, // (Optional) Tracks all passProps as properties. Defaults to undefined routeToProperties: (name, properties) => properties, }, captureScreens: true, }); }); ``` #### With `expo-router`: Check out and set it up the official way for [Screen tracking for analytics](https://docs.expo.dev/router/reference/screen-tracking/). Then call the `screen` method within the `useEffect` callback. React Native ```jsx const posthog = usePostHog() // use the usePostHog hook if using the PostHogProvider or your own custom posthog instance posthog.screen(pathname, params) ``` #### Manually capturing screen capture events If you prefer not to use autocapture, you can manually capture screen views by calling `posthog.screen()`. This function requires a `name`. You may also pass in an optional `properties` object. JavaScript ```javascript posthog.screen('dashboard', { background: 'blue', hero: 'superhog', }) ``` ## Autocapture PostHog autocapture can automatically track the following events for you: - **Application Opened** – when the app is opened from a closed state - **Application Became Active** – when the app comes to the foreground (e.g. from the app switcher) - **Application Backgrounded** – when the app is sent to the background by the user - **Application Installed** – when the app is installed. - **Application Updated** – when the app is updated. - **$screen** – when the user navigates (if using `@react-navigation/native` (v6 or lower) or `react-native-navigation`), check out the [capturing screen views](/docs/libraries/react-native.md#capturing-screen-views) section - **$autocapture** – touch events when the user interacts with the screen - **$exception** – when the app throws exceptions. > ⚠️ **React Navigation v7 users** > > React Navigation v7 restricts navigation hooks (such as `useNavigationState`) to components rendered inside a Screen that belongs to a Navigator. > > Because of this change, automatic screen tracking may throw errors if PostHog is initialized outside a screen context. This commonly affects apps upgrading from React Navigation v6 to v7. > > For React Navigation v7, we recommend disabling automatic screen capture for screens and manually calling `posthog.screen()` inside each screen component. See the [Capturing screen views](/docs/libraries/react-native.md#capturing-screen-views) section below. Application lifecycle events are enabled by default. Screen capture is enabled by default in `PostHogProvider` unless you set `captureScreens: false`. Touch capture is disabled by default and requires `captureTouches: true`. When touch capture is enabled, touch events for children of `PostHogProvider` are tracked, capturing a snapshot of the view hierarchy at that point. This enables you to create [insights](/docs/product-analytics/insights.md) in PostHog without adding custom events. PostHog will try to generate a sensible name for touched elements based on the React component `displayName` or `name`. If you prefer, you can set your own name using the `ph-label` prop: React Native ```jsx ``` ### Autocapture configuration React Native ```jsx { if (params.id) return `${name}/${params.id}` return name }, routeToProperties: (name, params) => { if (name === "SensitiveScreen") return undefined return params }, }, }}> ... ``` ### Preventing sensitive data capture If there are elements you don't want to be captured, you can add the `ph-no-capture` property. If this property is found anywhere in the view hierarchy, the entire touch event is ignored: React Native ```jsx Sensitive view here ``` ### Capturing screen views With `captureScreens: true` (the default in `PostHogProvider`), PostHog captures a `$screen` event automatically when the user navigates, provided you're using `@react-navigation/native` (v6 or lower) or `react-native-navigation`. To manually send a screen capture event, use the `screen` method: React Native ```jsx posthog.screen('Dashboard', { fromIcon: 'bottom' }) ``` > **React Navigation v7 users:** automatic screen tracking may throw errors if PostHog is initialized outside a screen context. For v7, disable automatic screen capture (`captureScreens: false`) and call `posthog.screen()` manually inside each screen component. #### Filtering autocaptured screens You can stop specific screens from being autocaptured by filtering them in your before-send hook. Return `null` for any `$screen` event whose `$screen_name` matches a screen you don't want to track, and it's dropped before being sent – keeping unwanted screen views out of your event log. Because it's just a function, you can filter however you like – an **ignorelist** (drop the screens you name), an **allowlist** (invert the check to capture only the screens you name), or any custom rule such as a name prefix, a regex, or a check against the event's properties. `before_send` is a client option, so pass it via the provider's `options` prop (or to `new PostHog(...)` if you create the client yourself): React Native ```jsx const IGNORED_SCREENS = new Set(['Splash', 'Debug']) { if (event?.event === '$screen') { const screenName = event.properties?.['$screen_name'] return IGNORED_SCREENS.has(screenName) ? null : event } return event }, }} > {/* app */} ``` Swap the check for an allowlist (`return TRACKED_SCREENS.has(screenName) ? event : null`) if you'd rather capture only a specific set of screens. ## Common `before_send` patterns `before_send` accepts a single function or an array of functions that run in order, so you can compose several small hooks. Filtering screens is one use – here are a few others. **Drop a specific event.** Stop an internal or debug event from ever being sent: React Native ```jsx const posthog = new PostHog('', { before_send: (event) => { if (event?.event === 'debug_only_event') { return null // never send this event } return event }, }) ``` **Log events instead of sending them.** Handy while debugging what would be captured: React Native ```jsx const posthog = new PostHog('', { before_send: (event) => { console.log('[PostHog] would send', event?.event, event?.properties) return null // drop everything }, }) ``` **Redact sensitive properties.** Strip a value before it leaves the device: React Native ```jsx const posthog = new PostHog('', { before_send: (event) => { if (event?.properties?.email) { event.properties.email = '***' } return event }, }) ``` For more examples, see the [JavaScript Web SDK docs](/docs/libraries/js/usage.md#amending-or-sampling-events). ## Identifying users > We highly recommend reading our section on [Identifying users](/docs/integrate/identifying-users.md) to better understand how to correctly use this method. Using `identify`, you can associate events with specific users. This enables you to gain full insights as to how they're using your product across different sessions, devices, and platforms. An `identify` call has the following arguments: - **distinctId:** Required. A unique identifier for your user. Typically either their email or database ID. - **properties:** Optional. A dictionary with key:value pairs to set the [person properties](/docs/product-analytics/person-properties.md) React Native ```jsx posthog.identify('distinctID', { // ($set): email: 'user@posthog.com', name: 'My Name' } ) ``` `$set_once` works just like `$set`, except that it will **only set the property if the user doesn't already have that property set**. [See the difference between `$set` and `$set_once`](/docs/product-analytics/person-properties.md#what-is-the-difference-between-set-and-set_once) React Native ```jsx posthog.identify('distinctID', { $set: { email: 'user@posthog.com', name: 'My Name' }, $set_once: { date_of_first_log_in: '2024-03-01' } } ) ``` You should call `identify` as soon as you're able to. Typically, this is after your user logs in. This ensures that events sent during your user's sessions are correctly associated with them. When you call `identify`, all previously tracked [anonymous events](/docs/data/anonymous-vs-identified-events.md) will be linked to the user. ## Get the current user's distinct ID You may find it helpful to get the current user's distinct ID. For example, to check whether you've already called `identify` for a user or not. To do this, call `posthog.get_distinct_id()`. This returns either the ID automatically generated by PostHog or the ID that has been passed by a call to `identify()`. ## Alias Sometimes, you want to assign multiple distinct IDs to a single user. This is helpful when your primary distinct ID is inaccessible. For example, if a distinct ID used on the frontend is not available in your backend. In this case, you can use `alias` to assign another distinct ID to the same user. React Native ```jsx // Sets alias for current user posthog.alias('distinct_id') ``` We strongly recommend reading our docs on [alias](/docs/product-analytics/identify.md#alias-assigning-multiple-distinct-ids-to-the-same-user) to best understand how to correctly use this method. ## Setting person properties Person properties enable you to capture, manage, and analyze specific data about a user. You can use them to create [filters](/docs/product-analytics/trends.md#filtering-events-based-on-properties) or [cohorts](/docs/data/cohorts.md), which can then be used in [insights](/docs/product-analytics/insights.md), [feature flags](/docs/feature-flags.md), and more. To set a user's properties, include the `$set` or `$set_once` property when capturing any event: ### $set JavaScript ```javascript posthog.capture('some_event', { $set: { userProperty: 'value' } }) ``` ### $set\_once `$set_once` works just like `$set`, except it **only sets the property if the user doesn't already have that property set**. JavaScript ```javascript posthog.capture('some_event', { $set_once: { userProperty: 'value' } }) ``` You can also use `setPersonProperties()` and `unsetPersonProperties()` to manage person properties directly. See [person properties](/docs/product-analytics/person-properties.md) for examples. ## Super properties Super properties are properties associated with events that are set once and then sent with every `capture` call, be it a `$screen`, an autocaptured touch, or anything else. They are set using `posthog.register`, which takes a properties object as a parameter, and they persist across sessions. For example: JavaScript ```javascript posthog.register({ 'icecream pref': 'vanilla', team_id: 22, }) ``` The call above ensures that every event sent by the user will include `"icecream pref": "vanilla"` and `"team_id": 22`. This way, if you filtered events by property using `icecream_pref = vanilla`, it would display all events captured on that user after the `posthog.register` call, since they all include the specified Super Property. This does **not** set the user's properties. This only sets the properties for their events. To store person properties, see the [setting person properties section](#setting-user-properties). ### Removing stored super properties Super Properties are persisted across sessions so you have to explicitly remove them if they are no longer relevant. In order to stop sending a Super Property with events, you can use `posthog.unregister`, like so: JavaScript ```javascript posthog.unregister('icecream pref'), ``` This will remove the super property and subsequent events will not include it. If you are doing this as part of a user logging out you can instead simply [`posthog.reset()`](#reset-after-logout) which takes care of clearing all stored Super Properties and more. ## Opt out of data capture You can completely opt users out from data capture by default or on a per-person basis. See [Opt in/out](#opt-inout) for the current React Native API. ## Flush You can configure how many events queue before flushing with `flushAt`. Setting this to `1` will send events immediately and will use more battery. The default is `20`. You can also configure the flush interval with `flushInterval`, in milliseconds (default `10000`), after which queued events are sent regardless of how many have been gathered: JavaScript ```javascript const posthog = new PostHog('', { flushAt: 20, flushInterval: 10000, }) ``` You can also manually flush the queue to start sending events immediately instead of waiting for the next batch: JavaScript ```javascript await posthog.flush() ``` If a flush is already in progress, it returns a promise for the existing flush. Flushing is best-effort and asynchronous – it starts sending queued events in the background but doesn't wait for the request to finish, so it isn't a delivery guarantee. ## Reset after logout To reset the user's ID and anonymous ID, call `reset`. Usually you would do this right after the user logs out. JavaScript ```javascript posthog.reset() ``` ## Offline behavior The PostHog React Native SDK will continue to capture events when the device is offline. When `persistence` is set to `file` (by default), the events are stored in a queue in the device's file storage. Even when the app is closed, the events are persisted and will be flushed when the app is opened again. - The queue has a maximum size defined by `maxQueueSize` in the configuration. - When the queue is full, the oldest event is deleted first. - The queue is flushed only when the device is online. ## Opt in/out By default, PostHog has tracking enabled unless it is forcefully disabled by default using the option `{ defaultOptIn: false }`. You can give your users the option to opt in or out by calling the relevant methods. Once these have been called they are persisted and will be respected until optIn/Out is called again or the `reset` function is called. To opt in/out of tracking, use the following calls. JavaScript ```javascript posthog.optedOut // See if a user has opted out posthog.optIn() // opt in posthog.optOut() // opt out ``` If you still wish capture these events but want to create a distinction between users and team in PostHog, you should look into [Cohorts](/docs/user-guides/cohorts.md#differentiating-team-vs-users-traffic). ## Feature Flags PostHog's [feature flags](/docs/feature-flags.md) enable you to safely deploy and roll back new features as well as target specific users and groups with them. There are two ways to implement feature flags in React Native: 1. Using hooks. 2. Loading the flag directly. ### Method 1: Using hooks #### Example 1: Boolean feature flags React Native ```jsx import { useFeatureFlag } from 'posthog-react-native' const MyComponent = () => { const booleanFlag = useFeatureFlag('key-for-your-boolean-flag') if (booleanFlag === undefined) { // the response is undefined if the flags are being loaded return null } // Optional use the 'useFeatureFlagWithPayload' hook for fetching the feature flag payload return booleanFlag ? Testing feature 😄 : Not Testing feature 😢 } ``` #### Example 2: Multivariate feature flags React Native ```jsx import { useFeatureFlag } from 'posthog-react-native' const MyComponent = () => { const multiVariantFeature = useFeatureFlag('key-for-your-multivariate-flag') if (multiVariantFeature === undefined) { // the response is undefined if the flags are being loaded return null } else if (multiVariantFeature === 'variant-name') { // replace 'variant-name' with the name of your variant // Do something } // Optional use the 'useFeatureFlagWithPayload' hook for fetching the feature flag payload return
} ``` ### Method 2: Loading the flag directly React Native ```jsx // Defaults to undefined if not loaded yet or if there was a problem loading posthog.isFeatureEnabled('key-for-your-boolean-flag') // Defaults to undefined if not loaded yet or if there was a problem loading posthog.getFeatureFlag('key-for-your-boolean-flag') // Multivariant feature flags are returned as a string posthog.getFeatureFlag('key-for-your-multivariate-flag') // Optional: fetch the payload (returns 'JsonType' or undefined if not loaded yet or if there was a problem loading) posthog.getFeatureFlagResult('key-for-your-multivariate-flag')?.payload ``` ### Inspecting all feature flags You can inspect all currently loaded feature flags with `getAllFeatureFlags()`. It returns each flag's `key`, `enabled` state, `variant`, and `payload`, and does not send a `$feature_flag_called` event, so calling it won't affect your experiment results or flag usage analytics: React Native ```jsx for (const flag of posthog.getAllFeatureFlags()) { console.log(flag.key, flag.enabled, flag.variant, flag.payload) } ``` ### Ensuring flags are loaded before usage Every time a user opens the app, we send a request in the background to fetch the feature flags that apply to that user. We store those flags in the storage. This means that for most screens, the feature flags are available immediately — **except for the first time a user visits**. To handle this, you can use the `onFeatureFlags` callback to wait for the feature flag request to finish: React Native ```jsx posthog.onFeatureFlags((flags) => { // feature flags are guaranteed to be available at this point if (posthog.isFeatureEnabled('flag-key')) { // do something } }) ``` ### Reloading flags PostHog loads feature flags when instantiated and refreshes whenever methods are called that affect the flag. If want to manually trigger a refresh, you can call `reloadFeatureFlagsAsync()`: React Native ```jsx posthog.reloadFeatureFlagsAsync().then((refreshedFlags) => console.log(refreshedFlags)) ``` Or when you want to trigger the reload, but don't care about the result: React Native ```jsx posthog.reloadFeatureFlags() ``` ### Feature flag caching The React Native SDK caches feature flag values in AsyncStorage. Cached values persist indefinitely with no TTL until updated by a successful API call. This enables offline support and reduces latency, but means **inactive users may see stale flag values** from their last session. For example, if a user last opened your app when a flag was `false`, that value remains cached even after you roll it out to 100%. When they reopen the app, the SDK returns the cached `false` first, then fetches the fresh `true` value from the API. To ensure fresh flag values: React Native ```jsx // Force refresh on app start await posthog.reloadFeatureFlagsAsync() ``` Or clear cached values for inactive users: React Native ```jsx if (lastActiveDate < migrationDate) { posthog.reset() // Clears all cached data } ``` ### Request timeout You can configure the `featureFlagsRequestTimeoutMs` parameter when initializing your PostHog client to set a flag request timeout. This helps prevent your code from being blocked in the case when PostHog's servers are too slow to respond. By default, this is set at 10 seconds. React Native ```jsx export const posthog = new PostHog('', { // usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com' host: 'https://us.i.posthog.com', featureFlagsRequestTimeoutMs: 10000 // Time in milliseconds. Default is 10000 (10 seconds). }) ``` ### Error handling When using the PostHog SDK, it's important to handle potential errors that may occur during feature flag operations. Here's an example of how to wrap PostHog SDK methods in an error handler: React Native ```jsx function handleFeatureFlag(client, flagKey, distinctId) { try { const isEnabled = client.isFeatureEnabled(flagKey, distinctId); console.log(`Feature flag '${flagKey}' for user '${distinctId}' is ${isEnabled ? 'enabled' : 'disabled'}`); return isEnabled; } catch (error) { console.error(`Error fetching feature flag '${flagKey}': ${error.message}`); // Optionally, you can return a default value or throw the error // return false; // Default to disabled throw error; } } // Usage example try { const flagEnabled = handleFeatureFlag(client, 'new-feature', 'user-123'); if (flagEnabled) { // Implement new feature logic } else { // Implement old feature logic } } catch (error) { // Handle the error at a higher level console.error('Feature flag check failed, using default behavior'); // Implement fallback logic } ``` ### Overriding server properties Sometimes, you might want to evaluate feature flags using properties that haven't been ingested yet, or were set incorrectly earlier. You can do so by setting properties the flag depends on with these calls: React Native ```jsx posthog.setPersonPropertiesForFlags({'property1': 'value', property2: 'value2'}) ``` Note that these are set for the entire session. Successive calls are additive: all properties you set are combined together and sent for flag evaluation. Whenever you set these properties, we also trigger a reload of feature flags to ensure we have the latest values. You can disable this by passing in the optional parameter for reloading: React Native ```jsx posthog.setPersonPropertiesForFlags({'property1': 'value', property2: 'value2'}, false) ``` At any point, you can reset these properties by calling `resetPersonPropertiesForFlags`: React Native ```jsx posthog.resetPersonPropertiesForFlags() ``` The same holds for [group](/docs/product-analytics/group-analytics.md) properties: React Native ```jsx // set properties for a group posthog.setGroupPropertiesForFlags({'company': {'property1': 'value', property2: 'value2'}}) // reset properties for all groups: posthog.resetGroupPropertiesForFlags() ``` > **Note:** You don't need to add the group names here, since these properties are automatically attached to the current group (set via `posthog.group()`). When you change the group, these properties are reset. **Automatic overrides** Whenever you call `posthog.identify` with person properties, we automatically add these properties to flag evaluation calls to help determine the correct flag values. The same is true for when you call `posthog.group()`. **Default overridden properties** By default, we always override some properties based on the user IP address. The list of properties that this overrides: 1. $geoip\_city\_name 2. $geoip\_country\_name 3. $geoip\_country\_code 4. $geoip\_continent\_name 5. $geoip\_continent\_code 6. $geoip\_postal\_code 7. $geoip\_time\_zone This enables any geolocation-based flags to work without manually setting these properties. ### Bootstrapping flags Since there is a delay between initializing PostHog and fetching feature flags, feature flags are not always available immediately. This makes them unusable if you want to do something like redirecting a user to a different page based on a feature flag. To have your feature flags available immediately, you can initialize PostHog with precomputed values until it has had a chance to fetch them. This is called bootstrapping. After the SDK fetches feature flags from PostHog, it will use those flag values instead of bootstrapped ones. Pass `bootstrap` in the initialization options to seed identity and flag values: React Native ```jsx ``` See [bootstrapping Feature Flags](/docs/feature-flags/bootstrapping.md) for server-side evaluation and flag lifecycle, and [SDK bootstrapping](/docs/libraries/bootstrapping.md) for cross-SDK identity behavior. ### Supplying flags from your own backend If you evaluate feature flags outside the SDK – for example on your own server with [`posthog-node` local evaluation](/docs/feature-flags/local-evaluation.md), then pass the results into your app – you can have the SDK use those values and never fetch flags itself. Set `disableRemoteFeatureFlags: true` so the SDK never requests `/flags` (including the refetches that `identify()`, `group()`, and `reset()` normally trigger), then push your evaluated flags at runtime with `updateFlags(flags, payloads?, { merge })`: React Native ```jsx const posthog = new PostHog('', { host: 'https://us.i.posthog.com', // Don't fetch or evaluate flags on-device – we supply them ourselves. disableRemoteFeatureFlags: true, // Optional: values that must be available at startup, before updateFlags() runs. // Without this, reads return their not-loaded defaults until you push flags. bootstrap: { featureFlags: { 'my-flag': true }, featureFlagPayloads: { 'my-flag': { color: 'blue' } }, }, }) // Later – e.g. after login, once your backend has evaluated flags for this user: posthog.updateFlags( { 'my-flag': true, 'my-variant-flag': 'test' }, { 'my-flag': { color: 'blue' } } ) posthog.getFeatureFlag('my-variant-flag') // 'test' posthog.getFeatureFlagResult('my-flag')?.payload // { color: 'blue' } ``` `updateFlags` replaces the stored flags by default; pass `{ merge: true }` to merge into the existing set instead. Values persist across app restarts, and `getFeatureFlag()` / `getFeatureFlagResult()` read them back like any other flag. Note that `reset()` (called on logout) clears the supplied flags, so re-push them with `updateFlags()` after the next identity change. Use `bootstrap` for any flag values that must be available at startup before `updateFlags()` runs. ## Experiments (A/B tests) Since [experiments](/docs/experiments/start-here.md) use feature flags, the code for running an experiment is very similar to the feature flags code. See [adding experiment code](/docs/experiments/adding-experiment-code.md) for React Native examples. It's also possible to [run experiments without using feature flags](/docs/experiments/running-experiments-without-feature-flags.md). ## Group analytics Group analytics allows you to associate the events for that person's session with a group (e.g. teams, organizations, etc.). See [Group Analytics](/docs/product-analytics/group-analytics.md) for implementation details. > **Note:** This is a paid feature and is not available on the open-source or free cloud plan. Learn more on the [pricing page](/pricing.md). ## Error tracking To set up error tracking in your project, see the [error tracking docs](/docs/error-tracking.md). ### Native crash autocapture JavaScript autocapture only covers exceptions thrown in your JS/TS code. To capture crashes in native code – a native module, the React Native runtime, or a C/C++ library – install the optional `@posthog/react-native-plugin` package and enable one or both of these options. What each option covers depends on the platform: | Option | iOS | Android | | --- | --- | --- | | `nativeCrashes` | Every native crash: uncaught Objective-C exceptions, Mach exceptions, and signals such as `SIGSEGV` and `SIGABRT`, including crashes in C/C++ code | Java/Kotlin crashes only | | `androidNdkCrashes` | Ignored | C/C++ (NDK) crashes, on Android 12 (API 31) or later. Requires `posthog-react-native` 4.78.0 and `@posthog/react-native-plugin` 2.12.0 or later | To capture every native crash on both platforms, enable both: React Native ```jsx const posthog = new PostHog('', { host: 'https://us.i.posthog.com', errorTracking: { autocapture: { nativeCrashes: true, androidNdkCrashes: true, }, }, }) ``` Native capture is gated by your project's **Enable exception autocapture** setting, and crash reports need native debug symbols uploaded at build time to produce readable stack traces. An Android NDK crash kills the process immediately, so PostHog captures it on the next app launch from the records the OS kept. Properties such as `$app_version` and the identity describe that launch, not the crash. The first launch after you enable `androidNdkCrashes` also captures NDK crashes the OS still holds from before. Follow the [React Native installation guide](/docs/error-tracking/installation/react-native.md) for the full setup, and [native crash symbolication](/docs/error-tracking/upload-source-maps/react-native.md#native-crash-symbolication) to upload symbols. ### Error boundaries You can use the `PostHogErrorBoundary` component to capture React rendering errors thrown by components: React Native ```jsx import { PostHogProvider, PostHogErrorBoundary } from 'posthog-react-native' import { View, Text } from 'react-native' const App = () => { return ( ) } const YourFallbackComponent = ({ error, componentStack }) => { return ( Something went wrong! {error instanceof Error ? error.message : String(error)} ) } ``` The `fallback` prop accepts a component to render when an error occurs. The `additionalProperties` prop lets you add custom properties to the captured error event. **Duplicate errors with console capture** If you have both `PostHogErrorBoundary` and `console` capture enabled in your `errorTracking` config, render errors will be captured twice. This is because React logs all errors to the console by default. To avoid this, set `console: []` on `errorTracking.autocapture` (for example, `errorTracking: { autocapture: { console: [] } }`) when using `PostHogErrorBoundary`. ### Customizing exception capture with before\_send You can use the `before_send` callback to modify, filter, or suppress exception events before they are sent to PostHog. This is useful for: - Adding custom properties to exceptions - Overriding exception fingerprints for custom grouping - Suppressing specific types of exceptions - Redacting sensitive information React Native ```jsx const posthog = new PostHog('', { host: 'https://us.i.posthog.com', before_send: (event) => { if (event.event === '$exception') { const exceptionList = event.properties?.['$exception_list'] || [] const exception = exceptionList.length > 0 ? exceptionList[0] : null if (exception) { // Add custom properties event.properties['custom_property'] = 'custom_value' // Override fingerprint for custom grouping event.properties['$exception_fingerprint'] = 'MyCustomGroup' } // Suppress specific exception types if (exception?.['$exception_type'] === 'IgnoredError') { return null // Drop the event } } return event }, }) ``` You can also use `before_send` to sample or filter other event types. See the [JavaScript Web SDK documentation](/docs/libraries/js/usage.md#amending-or-sampling-events) for more examples. ## Logs To set up [logs](/docs/logs.md) in your React Native app, follow the [React Native logs installation guide](/docs/logs/installation/react-native.md). The SDK exposes `posthog.captureLog`, `posthog.logger.{trace,debug,info,warn,error,fatal}`, and `posthog.flushLogs` for sending structured records to PostHog Logs. > **Minimum version:** `posthog-react-native@4.44.0` or later. ## Session replay To set up [session replay](/docs/session-replay/mobile.md) in your project, all you need to do is install the React Native SDK and the Session replay plugin, then follow the [instructions to enable Session Replay](/docs/session-replay/installation/react-native.md) for React Native. ## Surveys To set up surveys, follow the [additional installation instructions for React Native](/docs/surveys/installation/react-native.md). Surveys launched with [popover presentation](/docs/surveys/creating-surveys.md#presentation) are automatically shown to users matching the [display conditions](/docs/surveys/creating-surveys.md#display-conditions) you set up. > Note: URL and CSS selector targeting are not supported in React Native. Surveys that rely on these conditions will not appear. ## Push notifications The React Native SDK can register a device for [Workflows](/docs/workflows.md) push notifications and capture when a user opens one. For setup, including automatic and manual registration, capturing opens, opting out, and identity verification, see [Push notifications](/docs/workflows/push-notifications.md). ## Debug mode If you're not seeing the expected events being captured, the feature flags being evaluated, or the surveys being shown, you can enable debug mode to see what's happening. You can enable debug mode by setting the `debug` option to `true` in the `PostHogProvider` options. This will enable verbose logs about the inner workings of the SDK. React Native ```jsx ``` You can also call the `debug()` method in your code. React Native ```jsx posthog.debug() ``` ## Disabling for local development You may want to disable PostHog when working locally or in a test environment. You can do this by setting the `disable` option to `true` when initializing PostHog. Helpfully this allows you to continue using `usePostHog` and safely calling it without anything actually happening. React Native ```jsx // App.(js|ts) import { usePostHog, PostHogProvider } from 'posthog-react-native' ... export function MyApp() { return ( ) } const MyComponent = () => { const posthog = usePostHog() useEffect(() => { // Safe to call even when disabled! posthog.capture("mycomponent_loaded", { foo: "bar" }) }, []) } ``` ## Upgrading from V1, V2 to V3 or V3 to V4 V1 of this library utilised the underlying `posthog-ios` and `posthog-android` SDKs to do most of the work. Since the new version is written entirely in JS, using only Expo supported libraries, there are some changes to the way PostHog is configured as well as actually calling PostHog. For iOS, the new React Native SDK will attempt to migrate the previously persisted data (such as `distinctId` and `anonymousId`) which should result in no unexpected changes to tracked data. For Android, it is unfortunately not possible for persisted Android data to be loaded which means stored information such as the randomly generated `anonymousId` or the `distinctId` set by `posthog.identify` will not be present. For identified users, the simple workaround is to ensure that `identify` is called at least once when the app loads. For anonymous users there is unfortunately no straightforward workaround they will show up as new anonymous users in PostHog. Events such as `Application Installed` and `Application Updated` that require previously persisted data were unable to be migrated, the side effect being that you may see much higher numbers for `Application Installed` events. This is due to the fact that there is no native way of detecting a real "install" and as such, we store a marker the first time the SDK loads and treat that as an install. JSX ```jsx // DEPRECATED V1 Setup import PostHog from 'posthog-react-native' await PostHog.setup('', { // usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com' host: 'https://us.i.posthog.com', captureApplicationLifecycleEvents: false, // Replaced by 'PostHogProvider' captureDeepLinks: false, // No longer supported recordScreenViews: false, // Replaced by 'PostHogProvider' supporting @react-navigation/native flushInterval: 30, // Stays the same flushAt: 20, // Stays the same android: {...}, // No longer needed iOS: {...}, // No longer needed }) PostHog.capture("foo") // V2 Setup difference import PostHog from 'posthog-react-native' const posthog = await Posthog.initAsync('', { // usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com' host: 'https://us.i.posthog.com', // Add any other options here. }) // Use created instance rather than the PostHog class posthog.capture("foo") // V3 Setup difference import PostHog from 'posthog-react-native' const posthog = new PostHog('', { // usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com' host: 'https://us.i.posthog.com', // Add any other options here. }) // Use created instance rather than the PostHog class posthog.capture("foo") // V4 Setup difference import PostHog from 'posthog-react-native' const posthog = new PostHog('', { // usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com' host: 'https://us.i.posthog.com', // captureAppLifecycleEvents is enabled by default since version 4.39.0 (previously named `captureNativeAppLifecycleEvents` or `autocapture={{ captureLifecycleEvents: true }}`) // captureMode: 'json', // No longer supported // maskPhotoLibraryImages: true, // No longer supported }) posthog.setPersonPropertiesForFlags(...) // instead of `personProperties` posthog.setGroupPropertiesForFlags(...) // instead of `groupProperties` ``` ### Still have questions? Ask PostHog AI ### Was this page useful? HelpfulCould be better