Instrument and configure the React Native SDK
This page covers what you do after your first event: tracking, identity, sessions, Autocapture, and data delivery. For signatures and every option, go to the React Native SDK reference. For changes between versions, go to the React Native SDK release notes.
The examples import the SDK as amplitude:
import * as amplitude from "@amplitude/analytics-react-native";
Track events
Track a custom event
Pass an event name and optional properties:
amplitude.track("Song Played", {
title: "Happy Birthday",
duration: 145,
});
logEvent() is an alias of track().
Every event needs device_id or user_id. The SDK sets device_id for you. Amplitude rejects IDs shorter than 5 characters. To accept shorter IDs, set minIdLength. The SDK uses the HTTP V2 API, so its other field limits apply.
Pass the event as an object
Pass an object to set any event field directly, such as event-level groups:
amplitude.track({
event_type: "Song Played",
event_properties: { title: "Happy Birthday" },
groups: { orgId: "15" },
});
Override fields for one event
The third argument, eventOptions, sets fields on this event only. It doesn't change the SDK's stored identity.
amplitude.track("Song Played", { title: "Happy Birthday" }, {
user_id: "user-67890",
time: 1793919426000,
});
Set user properties
Build an Identify object, then send it with identify(). The SDK sends it as an $identify event.
const identifyEvent = new amplitude.Identify()
.set("plan", "premium")
.setOnce("initial_referrer", "newsletter")
.add("login_count", 1)
.append("visited_screens", "Settings")
.unset("trial_end");
amplitude.identify(identifyEvent);
Each operation applies to one property. Use one operation per property per Identify object: the SDK keeps the first operation for a property and ignores later ones on the same object. clearAll() removes every user property and discards every other operation on the object. You can't undo clearAll(). For each operation, go to Identify.
User property changes apply to events from that point forward. A profile in User Lookup shows the change right away, but charts show it only on events sent after the identify. For details, go to User properties and events.
Assign a user to a group
setGroup() assigns the current user to one or more groups. It also sets groupType as a user property with the group name as its value.
amplitude.setGroup("orgId", "15");
amplitude.setGroup("sport", ["soccer", "tennis"]);
To assign a group to one event only, without changing the user, pass groups in the event object.
Set group properties
groupIdentify() sets properties on a group, not on a user:
const groupProperties = new amplitude.Identify().set("plan", "enterprise");
amplitude.groupIdentify("orgId", "15", groupProperties);
Track revenue
Build a Revenue object and send it with revenue(). The SDK sends it as a revenue_amount event. Revenue is price × quantity, unless you call setRevenue().
const purchase = new amplitude.Revenue()
.setProductId("com.company.premium")
.setPrice(3.99)
.setQuantity(3)
.setRevenueType("purchase")
.setCurrency("USD");
amplitude.revenue(purchase);
For revenue verification, also call setReceipt() and setReceiptSig(). For every setter, go to Revenue.
Send events to more than one project
Create an instance for each project. Each instance has its own API key, identity, queue, and plugins. Give each one a unique instanceName so their stored data doesn't collide.
const secondary = amplitude.createInstance();
secondary.init("OTHER_API_KEY", undefined, {
instanceName: "secondary",
});
secondary.track("Song Played");
Pass the options as the third argument. init() reads the second argument as a user ID.
Identity and sessions
How the SDK chooses identifiers on load
init() restores identity from storage before it tracks anything. For each identifier, the first source that has a value wins.
- Device ID: the
deviceIdoption, then the stored value, then data from the maintenance SDK whenmigrateLegacyDatais on, then a new random UUID. - User ID: the
userIdargument or option, then the stored value, then maintenance SDK data. If none exists, events have nouser_id. - Session ID: the stored session, if it's still active. Otherwise a new session that starts at
init().
On iOS and Android, the SDK stores identity in AsyncStorage, so it survives app restarts. On react-native-web it uses a cookie, then localStorage. To use other storage, go to Store data somewhere other than AsyncStorage.
Set the user ID
Call setUserId() when a user signs in. Every later event carries the ID.
amplitude.setUserId("user-12345");
If you already know the user ID at startup, pass it to init() instead.
Reset identity when a user signs out
reset() clears the user ID and generates a new device ID, so the next user on the device appears as a new user. The session continues.
amplitude.reset();
Set the device ID
The SDK generates a random UUID as the device ID. To use your own identifier, call setDeviceId() with a value unique to the device, such as a UUID:
amplitude.setDeviceId("8d8a2e3c-2b6f-4f7e-9c1e-3a5b0c6d7e8f");
How sessions work
A session ID is the session's start time in milliseconds since the Unix epoch. All events in the session carry it as session_id.
The SDK starts a new session when:
init()runs and no stored session is still active.- The app returns to the foreground more than
sessionTimeout(5 minutes by default) after the last event. - The app tracks an event while it's in the background or inactive, more than
sessionTimeoutafter the last event.
While the app stays in the foreground, the session doesn't time out.
The SDK sends session_start and session_end events only when you turn on session Autocapture. session_end carries the previous session's ID.
Application lifecycle events in 1.13.0
In version 1.13.0, when autocapture.appLifecycles is on, returning to the foreground after sessionTimeout doesn't start a new session. The SDK tracks [Amplitude] Application Opened first, which refreshes the session before the timeout check. If you need session rollover on foreground, turn off appLifecycles, or start a session yourself with setSessionId().
Keep a session alive without an event
extendSession() records activity without sending an event, so the session's timeout starts over:
amplitude.extendSession();
Start a session with a specific ID
setSessionId() ends the current session and starts one with the ID you pass. Use a timestamp in milliseconds.
amplitude.setSessionId(Date.now());
After you call setSessionId(), the SDK keeps that session ID until you call it again or the app restarts. Sessions don't time out on their own in the meantime.
Opt users out of tracking
setOptOut(true) stops the SDK from sending events. The SDK stores the setting with identity, so it persists across app launches until you call setOptOut(false).
amplitude.setOptOut(true);
To start opted out, pass optOut: true to init().
Autocapture
Choose a starting point
Autocapture is off by default. Pass autocapture: true to turn on every feature, or an object to choose features. Features you leave out of the object stay off.
// Every Autocapture feature
amplitude.init("AMPLITUDE_API_KEY", undefined, {
autocapture: true,
});
// Only the features you list
amplitude.init("AMPLITUDE_API_KEY", undefined, {
autocapture: {
sessions: true,
appLifecycles: true,
screenViews: true,
},
});
Autocapture requires 1.6.6 or later. Some features require later versions. Go to Autocapture options.
Let Amplitude settings control Autocapture
To manage Autocapture from Amplitude's Autocapture settings instead of only in code, turn on remote configuration:
amplitude.init("AMPLITUDE_API_KEY", undefined, {
autocapture: true,
remoteConfig: { fetchRemoteConfig: true },
});
The SDK fetches the settings during init() and merges them over your local autocapture value. Remote settings apply only when local autocapture is true or an object. If you omit autocapture or set it to false, remote settings can't turn it on. Remote configuration is off by default in the React Native SDK. Requires 1.10.0 or later.
Features and events
| Feature | Option | Events |
|---|---|---|
| Sessions | sessions | session_start, session_end |
| Application lifecycles | appLifecycles | [Amplitude] Application Installed, [Amplitude] Application Updated, [Amplitude] Application Opened, [Amplitude] Application Backgrounded |
| Screen views | screenViews | [Amplitude] Screen Viewed, when you call the screen view helpers |
| Element interactions | elementInteractions | [Amplitude] Element Interacted, for handlers wrapped with ampCapture |
| Frustration interactions | frustrationInteractions | [Amplitude] Rage Click, [Amplitude] Error Click, for handlers wrapped with ampCapture |
| Network tracking | networkTracking | [Amplitude] Network Request |
For each event's properties, go to Autocaptured event properties.
Sessions
autocapture.sessions sends session_start and session_end whenever the session ID changes. For when that happens, go to How sessions work.
amplitude.init("AMPLITUDE_API_KEY", undefined, {
autocapture: { sessions: true },
});
The older trackingSessionEvents: true option does the same thing. Use autocapture.sessions for new code.
Application lifecycles
autocapture.appLifecycles tracks app state changes from React Native's AppState:
[Amplitude] Application Installed: The first launch for which the SDK has no stored app build (Android) or version and build (iOS). That includes the first launch after you turn onappLifecyclesfor existing users. Includes[Amplitude] Versionand[Amplitude] Build. Requires 1.6.8 or later.[Amplitude] Application Updated: The first launch after an update. On iOS, a change in version or build counts as an update. On Android, only a change in build does. Includes the current and previous version and build. Requires 1.6.8 or later.[Amplitude] Application Opened: The app is in the foreground atinit(), or returns to the foreground after it was in the background.[Amplitude] Application Backgrounded: The app moves to the background.
A change from inactive to active, such as closing Control Center on iOS, doesn't count as an open. The SDK doesn't track Installed and Updated events on react-native-web.
Screen views
Screen views aren't automatic. Call the helpers from your navigation code. Each call sends [Amplitude] Screen Viewed with the screen name in [Amplitude] Screen Name. Element interaction and frustration events also include the last screen name.
To track a screen by name:
amplitude.trackScreenView("Home");
With React Navigation, pass the navigation state to trackScreenViewOnNavigationStateChange(). onStateChange doesn't fire for the first screen, so also call it from onReady:
import {
NavigationContainer,
useNavigationContainerRef,
} from "@react-navigation/native";
import * as amplitude from "@amplitude/analytics-react-native";
export function App() {
const navigationRef = useNavigationContainerRef();
return (
<NavigationContainer
ref={navigationRef}
onReady={() =>
amplitude.trackScreenViewOnNavigationStateChange(
navigationRef.getRootState(),
)
}
onStateChange={amplitude.trackScreenViewOnNavigationStateChange}
>
{/* navigators */}
</NavigationContainer>
);
}
The helper finds the focused screen in nested navigators. It skips a call when the focused screen hasn't changed, such as onReady followed by onStateChange, or a change to route params only. reset() and a new init() clear that memory, so the current screen can send again.
The helpers work without autocapture.screenViews. To turn them off, for example in a build where you don't want screen views, set screenViews: false.
Element interactions
autocapture.elementInteractions sends [Amplitude] Element Interacted each time a handler wrapped with ampCapture() runs. Wrap handlers yourself, or let the Babel plugin wrap them at build time. Requires 1.6.8 or later.
amplitude.init("AMPLITUDE_API_KEY", undefined, {
autocapture: { elementInteractions: true },
});
import { Button } from "react-native";
import { ampCapture } from "@amplitude/analytics-react-native";
<Button
title="Sign up"
testID="sign-up"
accessibilityLabel="Sign up"
onPress={ampCapture(handleSignUp, {
action: "Press",
testID: "sign-up",
accessibilityLabel: "Sign up",
element: "Button",
component: "SignUpScreen",
})}
/>;
Each property you pass becomes an event property: action becomes [Amplitude] Action, testID becomes [Amplitude] Target Test ID, and so on. Use the action names the Babel plugin uses, such as Press and LongPress, with that capitalization. Frustration interactions count only Press and LongPress.
Wrap handlers with the Babel plugin
The @amplitude/babel-plugin-autocapture-transformer plugin wraps handlers on common interactive components with ampCapture() at build time, and adds the ampCapture import for you.
Install the plugin as a dev dependency:
bashnpm install --save-dev @amplitude/babel-plugin-autocapture-transformerAdd it to
babel.config.js:jsmodule.exports = function (api) { api.cache(true); return { presets: ["babel-preset-expo"], // or your existing presets plugins: ["@amplitude/babel-plugin-autocapture-transformer"], }; };Restart Metro with a clean cache, for example
npx react-native start --reset-cacheornpx expo start --clear.
The plugin rewrites a handler like this:
// Your code
<Button testID="12345" accessibilityLabel="Sign up" title="Sign up" onPress={handleSignUp} />
// After the plugin runs, inside a component named SignUpScreen
<Button
testID="12345"
accessibilityLabel="Sign up"
title="Sign up"
onPress={ampCapture(handleSignUp, {
action: "Press",
accessibilityLabel: "Sign up",
testID: "12345",
element: "Button",
component: "SignUpScreen",
})}
/>
The plugin wraps these components and handlers:
| Category | Components | Handler → action |
|---|---|---|
| Pressable | Button, Pressable, TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback, TouchableNativeFeedback, Touchable, Link | onPress → Press, onLongPress → LongPress |
| Value change | Switch, Slider, Picker | onValueChange → ValueChange |
| Text change | TextInput | onChangeText → ChangeText, onSubmitEditing → SubmitEditing |
To include your own components, list their names in the plugin options:
plugins: [
[
"@amplitude/babel-plugin-autocapture-transformer",
{
pressableElements: ["CustomButton"],
valueChangeElements: ["Toggle"],
textChangeElements: ["SearchField"],
},
],
],
The plugin matches plain JSX names only. It skips <RN.Button>-style member names, handlers written as strings, and testID or accessibilityLabel passed through a spread such as {...props}. It leaves out component when the enclosing function is anonymous.
Frustration interactions
autocapture.frustrationInteractions detects two patterns on handlers wrapped with ampCapture():
- Rage clicks: Four or more presses within 1 second, all within 50 pixels of each other. The SDK sends one
[Amplitude] Rage Clickwith the element properties from the first press. Requires 1.11.0 or later. - Error clicks: A press or long press followed by an unhandled JavaScript error within 2 seconds. The SDK sends
[Amplitude] Error Clickwith the error details. Experimental. Requires 1.12.0 or later.
amplitude.init("AMPLITUDE_API_KEY", undefined, {
autocapture: {
frustrationInteractions: true,
},
});
Both detectors are on when you pass true. To turn one off, pass an object:
amplitude.init("AMPLITUDE_API_KEY", undefined, {
autocapture: {
frustrationInteractions: { rageClick: true, errorClick: false },
},
});
Rage clicks need the press position, so the wrapped handler must receive the press event (with nativeEvent.pageX and nativeEvent.pageY), and action must be Press. Error clicks hook React Native's global error handler and pass each error on to the handler you had before.
Network tracking
autocapture.networkTracking sends [Amplitude] Network Request for failed requests your JavaScript makes with fetch or XMLHttpRequest. With networkTracking: true, the SDK tracks responses with status codes 500 to 599 and ignores requests to Amplitude. The SDK doesn't capture requests that native modules make outside JavaScript. Requires 1.6.7 or later.
amplitude.init("AMPLITUDE_API_KEY", undefined, {
autocapture: { networkTracking: true },
});
To choose which requests to track and what to capture, pass an options object:
amplitude.init("AMPLITUDE_API_KEY", undefined, {
autocapture: {
networkTracking: {
ignoreHosts: ["*.example.com"],
captureRules: [
{
statusCodeRange: "400-599",
responseHeaders: true,
responseBody: { allowlist: ["error/**"] },
},
],
},
},
});
The SDK evaluates captureRules from last to first, so put rules for specific URLs or hosts at the end. Header capture with true records only safe headers. Body capture works on JSON bodies only and records nothing until you set an allowlist. For every option, go to NetworkTrackingOptions.
allowlist and excludelist take JSON pointer paths with an optional leading /. * matches one key, and ** matches any number of keys. The captured body keeps the original structure. For example, with this response body:
{ "a": "A", "b": { "c": "C", "d": { "e": "E", "f": "F" } }, "g": "G" }
allowlist: ["b/**"] captures {"b":{"c":"C","d":{"e":"E","f":"F"}}}.
Plugins
Add a plugin
A plugin is an object with a name, a type, and an execute() method. The SDK runs plugins in a fixed order: before plugins, then enrichment plugins (the default type), then destination plugins. A before or enrichment plugin returns the event, changed or not, or null to drop it.
This before plugin lowercases the user ID so every later plugin reads the normalized value:
amplitude.add({
name: "normalize-user-id",
type: "before",
execute: async (event) => {
if (event.user_id) {
event.user_id = event.user_id.toLowerCase();
}
return event;
},
});
A destination plugin sends events somewhere else. It returns a result instead of an event:
amplitude.add({
name: "my-destination",
type: "destination",
execute: async (event) => {
const response = await fetch("https://example.com/events", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(event),
});
return { event, code: response.status, message: response.statusText };
},
});
Remove a plugin by name with amplitude.remove("my-destination"). For setup() and teardown(), go to Plugin.
Amplitude publishes plugins for other products:
- Feature Experiment:
@amplitude/plugin-experiment-react-native. Go to Amplitude Analytics plugin. - Session Replay:
@amplitude/plugin-session-replay-react-native. Go to Session Replay React Native SDK plugin. For Segment, go to the Session Replay React Native Segment integration.
To add every product with one package, use the Unified SDK for React Native.
Set location
Amplitude resolves an event's location in this order:
city,country, andregionon the event.location_latandlocation_lngon the event.- The request's IP address.
The SDK doesn't collect precise location, because that needs permissions not every app has. To add it, set the fields in an enrichment plugin. For an example, go to the location plugin example.
If you set trackingOptions.ipAddress to false, Amplitude can't resolve location from IP. Set location fields yourself in that case.
Device properties
Control which device properties the SDK collects
The SDK adds device properties to every event. On iOS and Android its native module reads them. Turn individual properties off with trackingOptions:
amplitude.init("AMPLITUDE_API_KEY", undefined, {
trackingOptions: {
ipAddress: false,
carrier: false,
adid: false,
},
});
Every option except country is on by default. For the list, go to trackingOptions.
carrier works on Android. On iOS 16 and later, Apple returns no carrier information and has no replacement API.
Advertising identifiers
| Platform | Identifier | Option | Default | Notes |
|---|---|---|---|---|
| Android | App set ID | appSetId | On | Recommended. Resets when the user reinstalls the app. Requires play-services-appset. |
| Android | ADID | adid | On | Resets when the user opts out of personalized ads. Requires play-services-ads-identifier. |
| iOS | IDFV | idfv | On | Recommended. Resets when the user removes every app from the same vendor. |
| iOS | IDFA | None | — | The SDK doesn't collect IDFA. Set it yourself with an enrichment plugin. |
The Android identifiers come from Google Play services, which the SDK loads only if your app includes them. Add the libraries you need to android/app/build.gradle:
dependencies {
implementation "com.google.android.gms:play-services-appset:16.0.2"
implementation "com.google.android.gms:play-services-ads-identifier:18.0.1"
}
For ADID, also follow the Advertising ID setup for Android. To turn an identifier off, set its option to false in trackingOptions.
Amplitude no longer recommends IDFA. If you need it, use an enrichment plugin that reads it with a third-party library. Go to the IDFA plugin example.
Data delivery
How the SDK uploads events
The SDK queues each event, then uploads the queue flushIntervalMillis (1 second by default) after the first event enters it. Each request carries up to flushQueueSize (30 by default) events. Failed uploads retry up to flushMaxRetries (5) times. For results and retries, go to Read the upload result.
To tune the upload interval and batch size:
amplitude.init("AMPLITUDE_API_KEY", undefined, {
flushIntervalMillis: 20000,
flushQueueSize: 50,
});
flushQueueSize caps the batch size. It doesn't trigger an early upload.
Flush queued events
flush() sends every queued event now:
await amplitude.flush().promise;
Choose a data region
Set serverZone to "EU" for projects in Amplitude's EU data center. Use the API key from the EU project.
amplitude.init("AMPLITUDE_API_KEY", undefined, {
serverZone: "EU",
});
To send to the Batch API instead of HTTP V2, set useBatch: true. For each combination's URL, go to Check what the SDK sent.
Send events through a proxy
To send events to your own server, set serverUrl:
amplitude.init("AMPLITUDE_API_KEY", undefined, {
serverUrl: "https://analytics-proxy.example.com/2/httpapi",
});
When you set serverUrl, the SDK ignores serverZone and useBatch.
To control the request itself, for example to add headers, pass a transportProvider. Its send() method receives the URL and payload, and returns the parsed response:
amplitude.init("AMPLITUDE_API_KEY", undefined, {
transportProvider: {
async send(serverUrl, payload) {
const response = await fetch(serverUrl, {
method: "POST",
headers: { "Content-Type": "application/json", "X-Proxy-Token": "TOKEN" },
body: JSON.stringify(payload),
});
const body = await response.json();
return {
status: response.ok ? "success" : "failed",
statusCode: response.status,
body: {
eventsIngested: body.events_ingested,
payloadSizeBytes: body.payload_size_bytes,
serverUploadTime: body.server_upload_time,
},
};
},
},
});
For a full implementation that handles every status, go to the SDK's FetchTransport source.
Work offline
The SDK detects connectivity and holds events while the device is offline, then uploads them when it reconnects. Offline detection is on by default. Requires 1.6.0 or later.
- iOS: The native module uses
NWPathMonitor.pod installlinks it. - Android: The native module uses
ConnectivityManager. The SDK's manifest adds theACCESS_NETWORK_STATEpermission to your app. - react-native-web: The SDK uses
navigator.onLineand the browser'sonlineandofflineevents.
Queued events stay in the storageProvider, so they survive an app restart while offline.
To turn off detection, set offline to null. The SDK then uploads on its normal schedule, regardless of connection status:
amplitude.init("AMPLITUDE_API_KEY", undefined, {
offline: null,
});
Don't use OfflineDisabled
amplitude.Types.OfflineDisabled is undefined in the React Native SDK, so offline: amplitude.Types.OfflineDisabled leaves detection on. Use null.
With detection off, you can set offline yourself from your own network logic. Set it to true to hold uploads and false to resume.
Store data somewhere other than AsyncStorage
The SDK keeps two kinds of data, each in its own storage option:
storageProvider: The queue of events waiting to upload.cookieStorage: Identity and session state, such as the device ID, user ID, and session ID.
Both default to AsyncStorage on iOS and Android. To use another store, such as MMKV, SQLite, or an encrypted store, implement the Storage interface and pass both options:
amplitude.init("AMPLITUDE_API_KEY", undefined, {
storageProvider: myEventQueueStorage,
cookieStorage: myIdentityStorage,
});
If you override only storageProvider, identity still goes to AsyncStorage. If AsyncStorage isn't available either, the SDK keeps identity in memory, so every launch gets a new device ID.
To leave AsyncStorage out of your native build, exclude it from autolinking in react-native.config.js:
module.exports = {
dependencies: {
"@react-native-async-storage/async-storage": {
platforms: { ios: null, android: null },
},
},
};
The JavaScript package stays installed, so imports still resolve, but the SDK never calls it after you override both options. These steps cover iOS and Android only.
Send SDK logs to your own logger
Pass a loggerProvider to send SDK logs somewhere other than console, for example to collect errors in production. The logger must implement disable(), enable(logLevel), log(), warn(), error(), and debug().
amplitude.init("AMPLITUDE_API_KEY", undefined, {
logLevel: amplitude.Types.LogLevel.Error,
loggerProvider: {
disable: () => {},
enable: () => {},
log: () => {},
debug: () => {},
warn: (...args) => console.warn(...args),
error: (...args) => reportToErrorService(args),
},
});
The SDK calls your logger's enable() with logLevel during init(), and then sends it messages at every level. Filter by level in your logger.
Was this helpful?
For AI agents: a documentation index is available at /docs/llms.txt. Append .md to any page URL for markdown, or send Accept: text/markdown.