Note
Observability for Mobile is currently in private preview. Grafana Labs offers support on a best-effort basis, and breaking changes might occur prior to the feature being made generally available.
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.
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)
- 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:
- Sign in to your Grafana Cloud instance.
- Click Observability > Frontend.
- Click Create New, then select Mobile Application.
- Enter an Application name to identify this application, then click Next.
- 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:
npm install @grafana/faro-react-native
# or
yarn add @grafana/faro-react-nativeOptionally, install the tracing package for distributed tracing support, using the following command:
npm install @grafana/faro-react-native-tracingSession persistence is enabled by default and requires the optional peer dependency react-native-mmkv:
npm install react-native-mmkvRebuild 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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
- Run your app on a simulator or device and interact with it to generate activity.
- In Grafana Cloud, open the Frontend Observability app and select your mobile application.
- 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:
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 directXMLHttpRequestcalls, enable XHR tracing explicitly withtracingOptions.instrumentationOptions.enableXhrInstrumentation: true. React Native implementsfetch()on top ofXMLHttpRequest, so enabling both for the same request can produce two spans for one logical call. If your app uses bothfetch()and axios/XHR, trace one API surface only, or enable both and exclude overlapping URLs on one instrumentation withignoreUrls.
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:
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:
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
endIf 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:
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


