Skip to main content

Paylisher React Native

Last updated: May 7, 2025

Which features are available in this library?

FeatureAvailability
Event captureAvailable
AutocaptureAvailable
User identificationAvailable
Session replayAvailable
Feature flagsAvailable
Group analyticsAvailable
SurveysNot available
LLM observabilityNot available
Error trackingNot 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.

AttributeTypeDefaultDescription
hostStringhttps://analytics.paylisher.comPaylisher API host (usually https://analytics.paylisher.com.
flushAtNumber20Number of events to queue before sending (flushing).
flushIntervalNumber10000Interval in milliseconds between periodic flushes.
maxBatchSizeNumber100Max number of messages flushed as part of a single batch (must be higher than flushAt).
maxQueueSizeNumber1000Max number of cached messages in memory or local storage.
disabledBooleanfalseDisables SDK entirely (useful for local/test environments).
defaultOptInBooleantrueIf false, SDK will not track until optIn() is called.
sendFeatureFlagEventBooleantrueWhether to track that getFeatureFlag() was called (used in experiments).
preloadFeatureFlagsBooleantrueWhether to load feature flags during initialization.
bootstrapObjectObject containing distinctId, isIdentifiedId, featureFlags, and featureFlagPayloads. Ensures data is available as SDK loads.
fetchRetryCountNumber3Number of HTTP request retries.
fetchRetryDelayNumber3000Delay in ms between HTTP retry attempts.
requestTimeoutNumber10000Timeout in milliseconds for any call.
featureFlagsRequestTimeoutMsNumber10000Timeout in ms for feature flag calls.
sessionExpirationTimeSecondsNumber1800Session expiry timeout in seconds (defaults to 30 minutes).
captureModeStringformFormat for posting events: form (compressed) or json.
persistenceStringfileStorage type (file, customStorage, memory, etc.).
customAppPropertiesObject/FunctionnullCustom implementation or override function for common App properties.
customStorageObjectnullCustom async or sync storage (async-storage, expo-file-system, mmkv). Ignored if persistence is memory.
captureNativeAppLifecycleEventsBooleanfalseCaptures native events (Installed, Opened, Updated, etc.). Keep false if already using lifecycle capture elsewhere.
disableGeoipBooleanfalseDisables automatic GeoIP resolution for events and feature flags.
enableSessionReplayBooleanfalseEnables Session Replay recording on Android/iOS.
sessionReplayConfigObjectnullSession Replay configuration.
enablePersistSessionIdAcrossRestartBooleanfalsePersists $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 as project created, user signed up, or invite 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/native or react-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 identify as 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 $set or $set_once fields 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.
  • name is a special property used as the group's display name in the Paylisher UI.
  • If name is 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 Installed may 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")