---
title: Instrument and configure the React Native SDK
description: "Track events, manage identity and sessions, configure Autocapture, and control data delivery with the Amplitude React Native SDK."
product: analytics
lang: en
last_updated: 2026-11-10
token_estimate: 7735
---
# 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](https://amplitude.com/docs/sdks/analytics/react-native/react-native-sdk/reference). For changes between versions, go to the [React Native SDK release notes](https://amplitude.com/docs/sdks/analytics/react-native/react-native-sdk-release-notes).

> For AI agents: a documentation index is available at [/docs/llms.txt](/docs/llms.txt). Append `.md` to any page URL for markdown, or send `Accept: text/markdown`.

The examples import the SDK as `amplitude`:

```ts
import * as amplitude from "@amplitude/analytics-react-native";
```

## Track events

### Track a custom event

Pass an event name and optional properties:

```ts
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`](https://amplitude.com/docs/sdks/analytics/react-native/react-native-sdk/reference#general-options). The SDK uses the [HTTP V2 API](https://amplitude.com/docs/apis/analytics/http-v2), so its other field limits apply.

#### Pass the event as an object

Pass an object to set any [event field](https://amplitude.com/docs/apis/analytics/http-v2#event-array-keys) directly, such as event-level `groups`:

```ts
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.

```ts
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.

```ts
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](https://amplitude.com/docs/sdks/analytics/react-native/react-native-sdk/reference#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](https://amplitude.com/docs/data/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.

```ts
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](#pass-the-event-as-an-object).

### Set group properties

`groupIdentify()` sets properties on a group, not on a user:

```ts
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()`.

```ts
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](https://amplitude.com/docs/sdks/analytics/react-native/react-native-sdk/reference#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.

```ts
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 `deviceId` option, then the stored value, then data from the [maintenance SDK](https://amplitude.com/docs/sdks/analytics/react-native/react-native-sdk-maintenance) when `migrateLegacyData` is on, then a new random UUID.
- **User ID**: the `userId` argument or option, then the stored value, then maintenance SDK data. If none exists, events have no `user_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](#store-data-somewhere-other-than-asyncstorage).

### Set the user ID

Call `setUserId()` when a user signs in. Every later event carries the ID.

```ts
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.

```ts
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:

```ts
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 `sessionTimeout` after 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](#sessions). `session_end` carries the previous session's ID.

> **Warning:** 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()`](#start-a-session-with-a-specific-id).

#### Keep a session alive without an event

`extendSession()` records activity without sending an event, so the session's timeout starts over:

```ts
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.

```ts
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)`.

```ts
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.

```ts
// 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](https://amplitude.com/docs/sdks/analytics/react-native/react-native-sdk/reference#autocapture-options).

### Let Amplitude settings control Autocapture

To manage Autocapture from Amplitude's Autocapture settings instead of only in code, turn on remote configuration:

```ts
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) | `sessions` | `session_start`, `session_end` |
| [Application lifecycles](#application-lifecycles) | `appLifecycles` | `[Amplitude] Application Installed`, `[Amplitude] Application Updated`, `[Amplitude] Application Opened`, `[Amplitude] Application Backgrounded` |
| [Screen views](#screen-views) | `screenViews` | `[Amplitude] Screen Viewed`, when you call the screen view helpers |
| [Element interactions](#element-interactions) | `elementInteractions` | `[Amplitude] Element Interacted`, for handlers wrapped with `ampCapture` |
| [Frustration interactions](#frustration-interactions) | `frustrationInteractions` | `[Amplitude] Rage Click`, `[Amplitude] Error Click`, for handlers wrapped with `ampCapture` |
| [Network tracking](#network-tracking) | `networkTracking` | `[Amplitude] Network Request` |

For each event's properties, go to [Autocaptured event properties](https://amplitude.com/docs/sdks/analytics/react-native/react-native-sdk/reference#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](#how-sessions-work).

```ts
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 on `appLifecycles` for existing users. Includes `[Amplitude] Version` and `[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 at `init()`, 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:

```ts
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`:

```tsx
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-handlers-with-the-babel-plugin) wrap them at build time. Requires 1.6.8 or later.

```ts
amplitude.init("AMPLITUDE_API_KEY", undefined, {
  autocapture: { elementInteractions: true },
});
```

```tsx
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.

1. Install the plugin as a dev dependency:

   ```bash
   npm install --save-dev @amplitude/babel-plugin-autocapture-transformer
   ```
2. Add it to `babel.config.js`:

   ```js
   module.exports = function (api) {
     api.cache(true);
     return {
       presets: ["babel-preset-expo"], // or your existing presets
       plugins: ["@amplitude/babel-plugin-autocapture-transformer"],
     };
   };
   ```
3. Restart Metro with a clean cache, for example `npx react-native start --reset-cache` or `npx expo start --clear`.

The plugin rewrites a handler like this:

```jsx
// 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:

```js
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 Click` with 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 Click` with the error details. Experimental. Requires 1.12.0 or later.

```ts
amplitude.init("AMPLITUDE_API_KEY", undefined, {
  autocapture: {
    frustrationInteractions: true,
  },
});
```

Both detectors are on when you pass `true`. To turn one off, pass an object:

```ts
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.

```ts
amplitude.init("AMPLITUDE_API_KEY", undefined, {
  autocapture: { networkTracking: true },
});
```

To choose which requests to track and what to capture, pass an options object:

```ts
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](https://amplitude.com/docs/sdks/analytics/react-native/react-native-sdk/reference#safe-headers). Body capture works on JSON bodies only and records nothing until you set an `allowlist`. For every option, go to [NetworkTrackingOptions](https://amplitude.com/docs/sdks/analytics/react-native/react-native-sdk/reference#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:

```json
{ "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:

```ts
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:

```ts
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](https://amplitude.com/docs/sdks/analytics/react-native/react-native-sdk/reference#plugin).

Amplitude publishes plugins for other products:

- Feature Experiment: `@amplitude/plugin-experiment-react-native`. Go to [Amplitude Analytics plugin](https://amplitude.com/docs/sdks/experiment-sdks/experiment-react-native#amplitude-analytics-plugin).
- Session Replay: `@amplitude/plugin-session-replay-react-native`. Go to [Session Replay React Native SDK plugin](https://amplitude.com/docs/sdks/session-replay/session-replay-react-native-sdk-plugin). For Segment, go to the [Session Replay React Native Segment integration](https://amplitude.com/docs/sdks/session-replay/session-replay-react-native-segment-integration).

To add every product with one package, use the [Unified SDK for React Native](https://amplitude.com/docs/sdks/analytics/react-native/unified-sdk/get-started).

### Set location

Amplitude resolves an event's location in this order:

1. `city`, `country`, and `region` on the event.
2. `location_lat` and `location_lng` on the event.
3. 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](https://github.com/amplitude/Amplitude-TypeScript/blob/v1.x/examples/plugins/react-native-get-location-plugin/LocationPlugin.ts).

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`:

```ts
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](https://amplitude.com/docs/sdks/analytics/react-native/react-native-sdk/reference#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`:

```groovy
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](https://amplitude.com/docs/sdks/analytics/android/android-kotlin-sdk/instrument-and-configure#advertising-id) 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](https://github.com/amplitude/Amplitude-TypeScript/blob/main/examples/plugins/react-native-idfa-plugin/idfaPlugin.ts).

## 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](https://amplitude.com/docs/sdks/analytics/react-native/react-native-sdk/verify-events#read-the-upload-result).

To tune the upload interval and batch size:

```ts
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:

```ts
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.

```ts
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](https://amplitude.com/docs/sdks/analytics/react-native/react-native-sdk/verify-events#check-what-the-sdk-sent).

### Send events through a proxy

To send events to your own server, set `serverUrl`:

```ts
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:

```ts
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](https://github.com/amplitude/Amplitude-TypeScript/blob/main/packages/analytics-core/src/transports/fetch.ts).

### 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 install` links it.
- **Android**: The native module uses `ConnectivityManager`. The SDK's manifest adds the `ACCESS_NETWORK_STATE` permission to your app.
- **react-native-web**: The SDK uses `navigator.onLine` and the browser's `online` and `offline` events.

Queued events stay in the [`storageProvider`](#store-data-somewhere-other-than-asyncstorage), 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:

```ts
amplitude.init("AMPLITUDE_API_KEY", undefined, {
  offline: null,
});
```

> **Warning:** 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](https://amplitude.com/docs/sdks/analytics/react-native/react-native-sdk/reference#storage) and pass both options:

```ts
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`:

```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()`.

```ts
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.

