---
title: "Instrument React Native Mobile applications | Grafana Cloud documentation"
description: "Instrument React Native applications with the Grafana Faro React Native SDK."
---

> For a curated documentation index, see [llms.txt](/llms.txt). For the complete documentation index, see [llms-full.txt](/llms-full.txt).

# Instrument React Native Mobile applications

> Note
> 
> The Grafana Faro React Native SDK, part of Observability for Mobile, is in Private Preview. To request access, add your information to the [Observability for Mobile preview wait list form](https://docs.google.com/forms/d/e/1FAIpQLSemWUuyeVwL7bepmuftEqcKj-aDarLJUgm7AqGBJq03S-wFGg/viewform).

The Grafana Faro React Native SDK is a library that collects observability data from your React Native application and sends it to Grafana Cloud.

Use this guide to add the Grafana Faro React Native SDK to your application, initialize it, and confirm data is flowing.

## Before you begin

Confirm the following before you instrument the application:

- A Grafana Cloud account ([sign up for free](/auth/sign-up/create-user))
- A React Native application for iOS or Android

## Create a mobile application in Grafana Cloud Frontend Observability

The application collects and analyzes telemetry from your mobile app. You use its name to tell it apart from your other applications. Mobile applications have their own creation flow, separate from web applications.

To create the application:

1. Sign in to your Grafana Cloud instance.
2. Click **Observability &gt; Frontend**.
3. Click **Create New**, then select **Mobile Application**.
4. Enter an **Application name** to identify this application, then click **Next**.
5. Under **Install and initialize SDK into your mobile app**, select the **React Native** platform tab.

## Install and initialize SDK into your mobile app

Now that you have created a Mobile Observability application, you need to instrument your mobile application.

Select **React Native** as your Platform. Then, use one of the following commands:

Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```bash
npm install @grafana/faro-react-native
# or
yarn add @grafana/faro-react-native
```

Optionally, install the tracing package for distributed tracing support, using the following command:

Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```bash
npm install @grafana/faro-react-native-tracing
```

Session persistence is enabled by default and requires the optional peer dependency `react-native-mmkv`:

Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```bash
npm install react-native-mmkv
```

Rebuild your native projects after installing. Without this dependency, the SDK falls back to in-memory sessions rather than persisting across app restarts.

## Initialize the SDK

Add the following code in your app’s entry point (for example, `App.tsx` or `index.js`) before any other JavaScript/TypeScript code. The wizard in Grafana Cloud generates this snippet for you, with your app name and endpoint URL:

tsx ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```tsx
import { initializeFaro } from '@grafana/faro-react-native';

initializeFaro({
  app: {
    name: 'your-app-name', // Must match the name you gave this application when you created it
    version: '1.0.0',
    environment: 'production', // Optional: development, staging, or production
  },
  // The endpoint URL generated when you created this application. The
  // collector token is embedded in the URL itself. There's no separate API key.
  url: '<your-collector-endpoint>',
});
```

## Additional configuration

The setup you just completed gets basic telemetry flowing, but none of it tells Grafana Cloud which screen a user was on, catches errors in your React component tree, correlates taps with the requests they trigger, or protects data collected while the device is offline.

The configurations in this section close those gaps. None of these configurations are required to see data in Grafana Cloud, but some of them, like navigation tracking and the error boundary are helpful to add because, without them, errors and performance data arrive with no screen context, and React rendering errors won’t be reported at all. Add the ones that match how your app is built.

### Navigation tracking

Without this configuration option, every error and performance metric Grafana Cloud receives has no indication of which screen the user was on, making it hard to tell whether a spike in errors is isolated to one screen or affecting the whole app.

Use the `useFaroNavigation` hook with a `NavigationContainer` ref to automatically track screen changes and send `view_changed` events when the route changes:

tsx ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```tsx
import { useNavigationContainerRef } from '@react-navigation/native';
import { useFaroNavigation } from '@grafana/faro-react-native';

function App() {
  const navigationRef = useNavigationContainerRef();
  useFaroNavigation(navigationRef);

  return <NavigationContainer ref={navigationRef}>{/* Your navigation */}</NavigationContainer>;
}
```

### Error boundary

The automatic error reporting catches unhandled JavaScript errors and promise rejections, but not errors thrown while a component renders. Those crash the screen (or the whole app) without ever being reported to Grafana Cloud.

Wrap your app (or critical sections) with `FaroErrorBoundary` to catch and report React component errors automatically:

tsx ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```tsx
import { FaroErrorBoundary } from '@grafana/faro-react-native';
import { Text } from 'react-native';

function App() {
  return (
    <FaroErrorBoundary fallback={<Text>Something went wrong</Text>}>
      <YourApp />
    </FaroErrorBoundary>
  );
}
```

### User action tracking

With this configuration, you can see not only that an error or a slow request happened, but also what the user was doing when it did. Correlating a failed request with the button that triggered it is what turns a vague error count into a reproducible issue.

Use the `withFaroUserAction` HOC to wrap touchable components and automatically track user interactions, with intelligent duration tracking and HTTP request correlation:

tsx ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```tsx
import { TouchableOpacity, Text } from 'react-native';
import { withFaroUserAction } from '@grafana/faro-react-native';

const SubmitButton = withFaroUserAction(TouchableOpacity, 'submit_form');

function MyForm() {
  return (
    <SubmitButton onPress={handleSubmit}>
      <Text>Submit</Text>
    </SubmitButton>
  );
}
```

### Offline transport

Mobile users lose connectivity far more often than web users do. With this configuration, any telemetry generated while offline, including errors that happen on a flight or in a poor-signal area, is not dropped and is delivered once the device reconnects.

Enable offline transport to cache telemetry events when the device is offline, and send them once connectivity is restored:

tsx ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```tsx
initializeFaro({
  // ...other config
  enableTransports: {
    fetch: true,
    offline: true,
  },
});
```

## Send custom data

The automatic instrumentation handles errors, performance, and navigation without any code changes, but it can’t know about your app’s specific business logic: a completed purchase, a failed login attempt, or a slow operation you want to measure yourself. The methods in this section let you send that context explicitly, so it shows up alongside the automatic telemetry in Grafana Cloud instead of living only in your own logs.

### Custom events

Use this method when something meaningful happened in your app that isn’t an error or a screen change (a purchase, a sign-up, a feature being used) and you want it visible alongside your other telemetry rather than buried in a separate analytics tool.

Track custom business events using `pushEvent`:

tsx ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```tsx
import { faro } from '@grafana/faro-react-native';

faro.api.pushEvent('purchase_completed', {
  productId: 'abc123',
  amount: '99.99',
});
```

### Custom logs

Automatic instrumentation captures console output and crashes, but not messages you’d want logged specifically for debugging or an audit trail.

Send custom log messages using `pushLog`:

tsx ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```tsx
import { faro, LogLevel } from '@grafana/faro-react-native';

faro.api.pushLog(['My log message']);
faro.api.pushLog(['Something went wrong'], { level: LogLevel.WARN });
```

### Custom measurements

Use this method to track the performance of something the SDK doesn’t measure automatically: a database query, a cache lookup, or any operation where you want a number tracked over time rather than just a pass/fail result.

Track performance metrics using `pushMeasurement`:

tsx ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```tsx
import { faro } from '@grafana/faro-react-native';

faro.api.pushMeasurement({
  type: 'heavy_operation',
  values: { duration: 1234 },
});
```

### Custom errors

Automatic error reporting and the error boundary catch exceptions that are thrown, but an error you catch and handle gracefully (a declined payment, a failed validation) never reaches Grafana Cloud unless you report it yourself.

Report errors manually using `pushError`:

tsx ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```tsx
import { faro } from '@grafana/faro-react-native';

try {
  await riskyOperation();
} catch (error) {
  faro.api.pushError(error);
}
```

### User identification

Without this method, every session is anonymous, and you can’t tell whether the sessions hitting a specific error or slow checkout are spread across many users or concentrated on one.

Associate telemetry with specific users using `setUser`. Clear it with `resetUser`:

tsx ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```tsx
import { faro } from '@grafana/faro-react-native';

faro.api.setUser({
  id: 'user-123',
  username: 'john_doe',
  email: 'john@example.com',
});

// To clear the user
faro.api.resetUser();
```

## Enable alerting (optional)

The setup wizard in Grafana Cloud offers a final, optional **Enable alerting** step to enable alerting for this application. Without it, you still see errors and performance data in Grafana Cloud, but nothing notifies you when something changes. You’d have to notice a regression by checking the app yourself.

Enabling alerts here means you find out when:

- **Errors count:** the number of errors increases day over day for the last 24 hours
- **New errors:** new errors appear in the last 24 hours
- **Slow app start:** cold start time exceeds 5000ms for the last 6 hours
- **High frozen frames:** the app UI freezes more than 10 times in the last 6 hours, causing the app to appear unresponsive
- **Low frame rate:** more than 10 slow frames occur in the last 6 hours (frame rates lower than 60fps), causing stuttering UI
- **High network latency:** API response times exceed 2000ms for the last 6 hours

You can select any combination of these when you complete the setup wizard, or skip this step and configure them later from the application’s settings page.

Click **Complete** to finish setup.

## Verify data is flowing

The fastest way to confirm the SDK is working, without waiting on Grafana Cloud ingestion, is to enable the console transport during development:

tsx ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```tsx
initializeFaro({
  // ...
  enableTransports: {
    fetch: true,
    console: true, // Logs outgoing telemetry to Metro
  },
});
```

Run your app and interact with it. You should see telemetry payloads logged to your Metro console. Turn `console` back off before shipping to production.

You can also confirm data in Grafana Cloud directly:

1. Run your app on a simulator or device and interact with it to generate activity.
2. In Grafana Cloud, open the **Frontend Observability** app and select your mobile application.
3. Confirm you see incoming sessions and performance data. It may take a minute or two for data to appear.

## Distributed tracing

If you installed `@grafana/faro-react-native-tracing` earlier, you can enable it with a single flag; you don’t need to add it to `instrumentations` separately:

tsx ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```tsx
initializeFaro({
  // ...
  enableTracing: true,
});
```

`fetch()` requests are then automatically traced and correlated with user actions, and you can create manual spans via `faro.otel`.

> Warning
> 
> By default, only `fetch()` is traced. If your app uses Axios or direct `XMLHttpRequest` calls, enable XHR tracing explicitly with `tracingOptions.instrumentationOptions.enableXhrInstrumentation: true`. React Native implements `fetch()` on top of `XMLHttpRequest`, so enabling both for the same request can produce two spans for one logical call. If your app uses both `fetch()` and axios/XHR, trace one API surface only, or enable both and exclude overlapping URLs on one instrumentation with `ignoreUrls`.

## Troubleshooting

The issues in this section are ones you’re likely to hit while following this exact setup, not a general error reference. If your build fails or data isn’t showing up after following this guide, check here before digging further on your own.

### iOS: Xcode 27 build error

If you’re building with Xcode 27, you may hit a build failure like:

text ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```text
The iOS Simulator deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 12.0, but the range of supported deployment target versions is 15.0 to 27.0.x. (in target 'PLCrashReporter-PLCrashReporter' from project 'Pods')
```

This happens because the SDK depends on PLCrashReporter, which declares iOS 12.0 as its minimum, and Xcode 27 requires 15.0 or higher. To fix it, add this to the `post_install` block in `ios/Podfile`, after `react_native_post_install`, then run `pod install`:

ruby ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```ruby
minimum_ios = Gem::Version.new('15.0')
if respond_to?(:min_ios_version_supported, true)
  minimum_ios = [minimum_ios, Gem::Version.new(min_ios_version_supported)].max
end
installer.pods_project.targets.each do |target|
  target.build_configurations.each do |build_config|
    deployment_target = build_config.build_settings['IPHONEOS_DEPLOYMENT_TARGET']
    if deployment_target && Gem::Version.new(deployment_target) < minimum_ios
      build_config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = minimum_ios.to_s
    end
  end
end
```

If your app’s own iOS deployment target is below 15.0, also raise it to 15.0 in Xcode.

### No data appearing in Grafana Cloud

First, check session sampling. If a session isn’t sampled, its traces (and other telemetry) aren’t sent:

tsx ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```tsx
const session = faro.api.getSession();
console.log('Is sampled:', session?.attributes?.isSampled);
```

Omit `sampling` from `sessionTracking` to record 100% of sessions, or set an explicit rate with `new SamplingRate(1)`.

### “OTEL is not available” error

This means `faro.otel` was accessed before tracing was initialized. Make sure `enableTracing: true` (or a `TracingInstrumentation` in `instrumentations`) is set in `initializeFaro()` before you use `faro.otel`.

### Trace IDs are all zeros

This means another OpenTelemetry tracer provider is already registered globally, so Faro’s tracing initialization can’t install its own. Remove the duplicate provider registration before initializing tracing.

## Next steps

- Explore your data in [Frontend Observability](/docs/grafana-cloud/observe-and-act/monitor-applications/frontend-observability/)
