Paylisher React Native
Last updated: May 7, 2025
Which features are available in this library?
| Feature | Availability |
|---|---|
| Event capture | Available |
| Autocapture | Available |
| User identification | Available |
| Session replay | Available |
| Feature flags | Available |
| Group analytics | Available |
| Surveys | Not available |
| LLM observability | Not available |
| Error tracking | Not available |
Installation
Our React Native SDK enables you to integrate Paylisher 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 paylisher-react-native package to your project as well as the required peer dependencies.
Expo apps
npx expo install paylisher-react-native expo-file-system expo-application expo-device expo-localization
React Native apps
yarn add paylisher-react-native @react-native-async-storage/async-storage react-native-device-info react-native-localize
# or
npm i -s paylisher-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 or React Native macOS, do not use the expo-file-system package since the Web and macOS targets aren't supported. Use the @react-native-async-storage/async-storage package instead.
Configuration
With the PaylisherProvider
The recommended way to set up Paylisher for React Native is to use the PaylisherProvider. This utilizes the Context API to pass the Paylisher client around, enable autocapture, and ensure that the queue is flushed at the right time.
Add it to your App.js or App.ts file:
import { usePaylisher, PaylisherProvider } from 'paylisher-react-native';
export function MyApp() {
return (
<PaylisherProvider apiKey="<ph_project_api_key>" options={{
host: 'https://analytics.paylisher.com',
}}>
<MyComponent />
</PaylisherProvider>
);
}
Then you can access Paylisher using the usePaylisher() hook:
const MyComponent = () => {
const paylisher = usePaylisher();
useEffect(() => {
paylisher.capture("event_name");
}, [paylisher]);
};
Without the PaylisherProvider
You can initialize Paylisher in its own file and import the instance:
// paylisher.ts
import Paylisher from 'paylisher-react-native';
export const paylisher = new Paylisher('<ph_project_api_key>', {
host: 'https://analytics.paylisher.com',
});
Then import the instance where needed:
import { paylisher } from './paylisher';
export function MyApp1() {
useEffect(() => {
paylisher.capture('event_name');
}, []);
return <View>Your app code</View>;
}
You can also use this instance with PaylisherProvider:
import { paylisher } from './paylisher';
export function MyApp() {
return (
<PaylisherProvider client={paylisher}>
{/* Your app code */}
</PaylisherProvider>
);
}
Configuration options
You can further customize how Paylisher works through its configuration on initialization.
| Attribute | Type | Default | Description |
|---|---|---|---|
| host | String | https://analytics.paylisher.com | Paylisher API host (usually https://analytics.paylisher.com. |
| flushAt | Number | 20 | Number of events to queue before sending (flushing). |
| flushInterval | Number | 10000 | Interval in milliseconds between periodic flushes. |
| maxBatchSize | Number | 100 | Max number of messages flushed as part of a single batch (must be higher than flushAt). |
| maxQueueSize | Number | 1000 | Max number of cached messages in memory or local storage. |
| disabled | Boolean | false | Disables SDK entirely (useful for local/test environments). |
| defaultOptIn | Boolean | true | If false, SDK will not track until optIn() is called. |
| sendFeatureFlagEvent | Boolean | true | Whether to track that getFeatureFlag() was called (used in experiments). |
| preloadFeatureFlags | Boolean | true | Whether to load feature flags during initialization. |
| bootstrap | Object | Object containing distinctId, isIdentifiedId, featureFlags, and featureFlagPayloads. Ensures data is available as SDK loads. | |
| fetchRetryCount | Number | 3 | Number of HTTP request retries. |
| fetchRetryDelay | Number | 3000 | Delay in ms between HTTP retry attempts. |
| requestTimeout | Number | 10000 | Timeout in milliseconds for any call. |
| featureFlagsRequestTimeoutMs | Number | 10000 | Timeout in ms for feature flag calls. |
| sessionExpirationTimeSeconds | Number | 1800 | Session expiry timeout in seconds (defaults to 30 minutes). |
| captureMode | String | form | Format for posting events: form (compressed) or json. |
| persistence | String | file | Storage type (file, customStorage, memory, etc.). |
| customAppProperties | Object/Function | null | Custom implementation or override function for common App properties. |
| customStorage | Object | null | Custom async or sync storage (async-storage, expo-file-system, mmkv). Ignored if persistence is memory. |
| captureNativeAppLifecycleEvents | Boolean | false | Captures native events (Installed, Opened, Updated, etc.). Keep false if already using lifecycle capture elsewhere. |
| disableGeoip | Boolean | false | Disables automatic GeoIP resolution for events and feature flags. |
| enableSessionReplay | Boolean | false | Enables Session Replay recording on Android/iOS. |
| sessionReplayConfig | Object | null | Session Replay configuration. |
| enablePersistSessionIdAcrossRestart | Boolean | false | Persists $session_id across app restarts (otherwise resets on each restart). |
Capturing events
You can send custom events using capture():
paylisher.capture('user_signed_up')
Tip: We recommend using an
[object] [verb]format for your event names, such asproject created,user signed up, orinvite sent.
Setting event properties
You can include additional metadata with events:
paylisher.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, screen tracking is automatically captured if the autocapture property is used in the PaylisherProvider.
Ensure PaylisherProvider is a child of NavigationContainer:
import { PaylisherProvider } from 'paylisher-react-native';
import { NavigationContainer } from '@react-navigation/native';
export function App() {
return (
<NavigationContainer>
<PaylisherProvider apiKey="<ph_project_api_key>" autocapture>
{/* Rest of app */}
</PaylisherProvider>
</NavigationContainer>
);
}
With react-native-navigation and autocapture
First, wrap screens with a shared PaylisherProvider:
import Paylisher, { PaylisherProvider } from 'paylisher-react-native';
import { Navigation } from 'react-native-navigation';
export const paylisher = new Paylisher('<ph_project_api_key>');
export const SharedPaylisherProvider = (props) => {
return (
<PaylisherProvider client={paylisher} autocapture={{
captureScreens: false,
captureLifecycleEvents: false,
captureTouches: true,
}}>
{props.children}
</PaylisherProvider>
);
};
Wrap each screen if you want to use touch capture or the usePaylisher() hook:
export const MyScreen = () => {
return (
<SharedPaylisherProvider>
<View>...</View>
</SharedPaylisherProvider>
);
};
Navigation.registerComponent('Screen', () => MyScreen);
Navigation.events().registerAppLaunchedListener(async () => {
paylisher.initReactNativeNavigation({
navigation: {
routeToName: (name, properties) => name,
routeToProperties: (name, properties) => properties,
},
captureScreens: true,
captureLifecycleEvents: true,
});
});
Manually capturing screen events
If you prefer not to use autocapture, manually capture screen views:
paylisher.screen('dashboard', {
background: 'blue',
hero: 'supercat',
})
Autocapture
Paylisher 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
- Application Installed – when the app is installed
- Application Updated – when the app is updated
- $screen – when navigation occurs (if using
@react-navigation/nativeorreact-native-navigation) - $autocapture – touch events when users interact with the UI
With autocapture, all touch events inside children of PaylisherProvider are tracked. A snapshot of the view hierarchy is also captured, enabling insights without needing to manually define events.
Paylisher will attempt to name the touched element using the React component’s displayName or name. You can override this by setting the ph-label prop:
<View ph-label="my-special-label"></View>
Autocapture configuration
You can adjust how autocapture works by passing custom options to the autocapture prop:
<PaylisherProvider apiKey="<ph_project_api_key>" autocapture={{
captureTouches: true,
captureLifecycleEvents: true,
captureScreens: true,
ignoreLabels: [],
customLabelProp: "ph-label",
noCaptureProp: "ph-no-capture",
propsToCapture: ["testID"],
navigation: {
routeToName: (name, params) => {
if (params.id) return `${name}/${params.id}`;
return name;
},
routeToProperties: (name, params) => {
if (name === "SensitiveScreen") return undefined;
return params;
}
}
}}>
...
</PaylisherProvider>
Preventing sensitive data capture
To prevent Paylisher from capturing sensitive views or areas, use the ph-no-capture prop. If this is found anywhere in the view hierarchy, the entire touch event will be ignored:
<View ph-no-capture>Sensitive view here</View>
Identifying users
We highly recommend reading our section on Identifying users 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 into 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.
paylisher.identify('distinctID',
{
email: '[email protected]',
name: 'My Name'
}
)
Using $set and $set_once
$set_once works just like $set, except that it will only set the property if the user doesn't already have that property set.
paylisher.identify('distinctID',
{
$set: {
email: '[email protected]',
name: 'My Name'
},
$set_once: {
date_of_first_log_in: '2024-03-01'
}
}
)
You should call
identifyas soon as you're able to — typically this is every time your app loads for the first time as well as directly 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 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.
const id = await paylisher.getDistinctId();
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.
paylisher.alias('distinct_id')
We strongly recommend reading our docs on alias 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 or cohorts, which can then be used in insights, feature flags, and more.
To set a user's properties, include the $set or $set_once property when capturing any event:
Using $set
paylisher.capture('some_event', {
$set: { userProperty: 'value' }
});
Using $set_once
$set_once works just like $set, except it only sets the property if the user doesn't already have that property set.
paylisher.capture('some_event', {
$set_once: { userProperty: 'value' }
});
Super properties
Super properties are properties associated with events that are set once and then sent with every capture call — whether a $screen, an autocaptured touch, or a custom event.
They are set using paylisher.register(), which takes a properties object as a parameter and persists across sessions:
paylisher.register({
'icecream pref': 'vanilla',
team_id: 22
});
This ensures that every event sent by the user will include "icecream pref": "vanilla" and "team_id": 22".
This does not set the user's properties. It only sets the properties for their events. To store person properties, use the
$setor$set_oncefields on events.
Removing stored super properties
Super Properties are persisted across sessions, so you must explicitly remove them when no longer relevant.
paylisher.unregister('icecream pref');
This will remove the super property. Future events will no longer include it.
To clear all super properties (e.g. after logout), use:
paylisher.reset();
Opt out of data capture
You can completely opt-out users from data capture in Paylisher. There are two main approaches:
1. Opt out users by default
Set opt_out_capturing_by_default to true when initializing Paylisher:
paylisher.init('<ph_project_api_key>', {
opt_out_capturing_by_default: true,
});
2. Opt out on a per-user basis
Call optOutCapturing() to opt a user out during runtime:
paylisher.optOutCapturing();
Opting users back in
To opt a user back in:
paylisher.optInCapturing();
Check if user has opted out
const hasOptedOut = await paylisher.hasOptedOutCapturing();
Flush
The number of events to queue before flushing can be configured with the flushAt option (default: 20).
You can also set a flush interval — by default, all events are flushed every 30 seconds.
To manually flush the queue:
await paylisher.flush();
If a flush is already in progress, the call will return the same ongoing Promise.
Reset after logout
To reset a user's ID and any anonymous tracking ID, call:
paylisher.reset();
Use this after a user logs out to clear identity and stored data.
Opt in/out utilities
By default, Paylisher has tracking enabled unless disabled with { defaultOptIn: false }.
You can let users control this:
const isOptedOut = paylisher.optedOut;
paylisher.optIn(); // opt user in
paylisher.optOut(); // opt user out
If you still want to track opt-out users for specific cases, consider using cohort-based segmentation.
Feature Flags
Paylisher's feature flags 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:
- Using hooks.
- Loading the flag directly.
Method 1: Using hooks
Example 1: Boolean feature flags
import { useFeatureFlag } from 'paylisher-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 ? <Text>Testing feature 😄</Text> : <Text>Not Testing feature 😢</Text>
}
Example 2: Multivariate feature flags
import { useFeatureFlag } from 'paylisher-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 <div/>
}
Method 2: Loading the flag directly
// Defaults to undefined if not loaded yet or if there was a problem loading
paylisher.isFeatureEnabled('key-for-your-boolean-flag')
// Defaults to undefined if not loaded yet or if there was a problem loading
paylisher.getFeatureFlag('key-for-your-boolean-flag')
// Multivariant feature flags are returned as a string
paylisher.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
paylisher.getFeatureFlagPayload('key-for-your-multivariate-flag')
Reloading flags
Paylisher 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():
paylisher.reloadFeatureFlagsAsync().then((refreshedFlags) => console.log(refreshedFlags))
Or when you want to trigger the reload, but don't care about the result:
paylisher.reloadFeatureFlags()
Request timeout
You can configure the featureFlagsRequestTimeoutMs parameter when initializing your Paylisher client to set a flag request timeout. This helps prevent your code from being blocked in the case when Paylisher's servers are too slow to respond. By default, this is set at 10 seconds.
export const paylisher = new Paylisher('<ph_project_api_key>', {
// usually 'https://analytics.paylisher.com'
host: 'https://analytics.paylisher.com',
featureFlagsRequestTimeoutMs: 10000 // Time in milliseconds. Default is 10000 (10 seconds).
})
Error handling
When using the Paylisher SDK, it's important to handle potential errors that may occur during feature flag operations. Here's an example of how to wrap Paylisher SDK methods in an error handler:
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:
paylisher.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:
paylisher.setPersonPropertiesForFlags({'property1': 'value', property2: 'value2'}, false)
At any point, you can reset these properties by calling:
paylisher.resetPersonPropertiesForFlags()
The same holds for group properties:
// set properties for a group
paylisher.setGroupPropertiesForFlags({'company': {'property1': 'value', property2: 'value2'}})
// reset properties for all groups:
paylisher.resetGroupPropertiesForFlags()
Note: You don't need to add the group names here, since these properties are automatically attached to the current group (set via paylisher.group()). When you change the group, these properties are reset.
Automatic overrides
Whenever you call paylisher.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 paylisher.group().
Default overridden properties
By default, we always override some properties based on the user IP address.
The list of properties that this overrides:
$geoip_city_name$geoip_country_name$geoip_country_code$geoip_continent_name$geoip_continent_code$geoip_postal_code$geoip_time_zone
Bootstrapping Flags
Since there is a delay between initializing Paylisher 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 Paylisher with precomputed values until it has had a chance to fetch them. This is called bootstrapping. After the SDK fetches feature flags from Paylisher, it will use those flag values instead of bootstrapped ones.
For details on how to implement bootstrapping, see our bootstrapping guide (replace with Paylisher version if applicable).
Experiments (A/B tests)
Since experiments use feature flags, the code for running an experiment is very similar to the feature flags code:
// With the useFeatureFlag hook
import { useFeatureFlag } from 'paylisher-react-native'
const MyComponent = () => {
const variant = useFeatureFlag('experiment-feature-flag-key')
if (variant === undefined) {
// the response is undefined if the flags are being loaded
return null
}
if (variant == 'variant-name') {
// do something
}
}
It's also possible to run experiments without using feature flags, but this approach gives you built-in targeting and evaluation through Paylisher’s platform.
Group analytics
Group analytics allows you to associate the events for that person's session with a group (e.g. teams, organizations, etc.).
Read the Group Analytics guide for more information. (Replace link with Paylisher docs if needed.)
⚠️ Note: This is a paid feature and is not available on the open-source or free cloud plan.
Associate the events for this session with a group
paylisher.group('company', 'company_id_in_your_db')
paylisher.capture('upgraded_plan')
// This event is associated with company ID `company_id_in_your_db`
- The first parameter is the group type (e.g., "company", "organization").
- The second parameter is the unique group ID.
- After this call, all events for the current session will be linked to this group.
Associate the events for this session with a group AND update the properties of that group
paylisher.group('company', 'company_id_in_your_db', {
name: 'Awesome Inc.',
employees: 11,
})
- The third parameter is an object that defines group properties.
nameis a special property used as the group's display name in the Paylisher UI.- If
nameis not specified, the group ID will be shown instead.
You can use these group properties for:
- filtering events
- creating cohorts
- targeting feature flags
Remember: Events sent before
group()is called will not be retroactively associated with a group.
You can reset the current group context by calling paylisher.group() again with different values or omitting the group to unset it.
Surveys
Surveys launched with popover presentation are automatically shown to users matching the display conditions you set up.
No manual integration is required if display conditions are correctly defined in your Paylisher dashboard.
Disabling for local development
You may want to disable Paylisher when working locally or in a test environment.
You can do this by setting the disabled option to true when initializing Paylisher.
This allows you to continue using usePaylisher() and safely calling it without anything actually happening.
// App.(js|ts)
import { usePaylisher, PaylisherProvider } from 'paylisher-react-native'
...
export function MyApp() {
return (
<PaylisherProvider apiKey="<ph_project_api_key>" options={{
// Disable Paylisher in development (or whatever other logic you choose)
disabled: __DEV__,
}}>
<MyComponent />
</PaylisherProvider>
)
}
const MyComponent = () => {
const paylisher = usePaylisher()
useEffect(() => {
// Safe to call even when disabled!
paylisher.capture("mycomponent_loaded", { foo: "bar" })
}, [])
}
Upgrading from V1 or V2 to V3
V1 of this library utilized the underlying paylisher-ios and paylisher-android SDKs to do most of the work.
Since the new version is written entirely in JavaScript using only Expo-supported libraries, there are changes to how Paylisher is configured and how you call it.
iOS
The new SDK attempts to migrate persisted data (e.g., distinctId, anonymousId) — so most users should not experience tracking disruption.
Android
Unfortunately, persisted data cannot be migrated.
That means:
- Anonymous users will be tracked as new anonymous users.
- Identified users: Call
identify()at least once when the app loads.
Events like
Application Installedmay show higher numbers, since there's no native way to detect a real install. The SDK treats the first load as a new install.
Deprecated V1 Setup
import Paylisher from 'paylisher-react-native'
await Paylisher.setup('<ph_project_api_key>', {
// usually 'https://analytics.paylisher.com'
host: 'https://analytics.paylisher.com',
captureApplicationLifecycleEvents: false, // Replaced by 'PaylisherProvider'
captureDeepLinks: false, // No longer supported
recordScreenViews: false, // Replaced by 'PaylisherProvider' supporting @react-navigation/native
flushInterval: 30, // Stays the same
flushAt: 20, // Stays the same
android: {...}, // No longer needed
iOS: {...}, // No longer needed
})
Paylisher.capture("foo")
V2 Setup difference
import Paylisher from 'paylisher-react-native'
const paylisher = await Paylisher.initAsync('<ph_project_api_key>', {
// usually 'https://analytics.paylisher.com'
host: 'https://analytics.paylisher.com',
// Add any other options here.
})
// Use created instance rather than the Paylisher class
paylisher.capture("foo")
V3 Setup difference
import Paylisher from 'paylisher-react-native'
const paylisher = new Paylisher('<ph_project_api_key>', {
// usually 'https://analytics.paylisher.com'
host: 'https://analytics.paylisher.com',
// Add any other options here.
})
// Use created instance rather than the Paylisher class
paylisher.capture("foo")