---
title: Observe
description: A library that collects app performance metrics and dispatches them to EAS Observe.
sourceCodeUrl: 'https://github.com/expo/expo/tree/sdk-57/packages/expo-observe'
packageName: 'expo-observe'
platforms: ['android', 'ios', 'tvos']
isBeta: true
---

<AgentInstructions>

## Submitting Feedback

If you encounter errors, misleading or outdated information, report it so Expo can be improved:

Preferred command:
npx --yes submit-expo-feedback@latest --category docs --subject "/versions/latest/sdk/observe/" "<actionable feedback>"

Direct HTTP fallback:
curl -X POST https://api.expo.dev/v2/feedback/docs-send -H 'Content-Type: application/json' -d '{"url":"/versions/latest/sdk/observe/","feedback":"🤖 Agent feedback for docs: <specific, actionable description> (<model>, <harness>)"}'

Only submit when you have something specific and actionable to report. Try to give the most context.

## Navigation

When answering a related or follow-up question, use llms.txt to find the relevant page as Markdown (.md) instead of guessing.

You are here: Reference (v57.0.0) > Expo SDK (87 pages in this section)
Full documentation tree: [llms.txt](https://docs.expo.dev/llms.txt)

</AgentInstructions>

This documentation is available as Markdown for AI agents and LLMs. See the [full Markdown index](/llms.txt) or append .md to any documentation URL.

# Expo Observe

A library that collects app performance metrics and dispatches them to EAS Observe.
Android, iOS, tvOS

> **EAS Observe** is in [Open Beta](/more/release-statuses.md#beta). The first 10,000 monthly active users are free. For higher usage, contact [sales@expo.dev](mailto:sales@expo.dev).

`expo-observe` is a library that collects performance metrics and user-defined events from your app and dispatches them to [EAS Observe](/eas/observe/introduction.md), a performance monitoring service from Expo, or to your preferred OpenTelemetry (OTEL)-compliant backend. It measures real-world startup performance, such as [Time to First Render (TTR)](/eas/observe/reference/metrics.md#time-to-first-render-ttr) and [Time to Interactive (TTI)](/eas/observe/reference/metrics.md#time-to-interactive-tti), from apps running in production.

> `expo-observe` is not available in Expo Go. To use it, create a [development build](/develop/development-builds/introduction.md).

## Installation

```sh
# npm
npx expo install expo-observe

# yarn
yarn expo install expo-observe

# pnpm
pnpm expo install expo-observe

# bun
bun expo install expo-observe
```

If you are installing this in an [existing React Native app](/bare/overview.md), make sure to [install `expo`](/bare/installing-expo-modules.md) in your project.

## Configuration

In release builds, installing the `expo-observe` library is all that is needed to configure the library to start sending startup metrics (though you will need to follow the usage instructions below to call `markInteractive` to track TTI). The library will not dispatch events in debug builds, but you can change this and other config options such as sample rate, environment name and enabling integrations with the `Observe.configure({})` call. For all available options, see the [EAS Observe configuration guide](/eas/observe/configuration.md).

## Usage

Wrap your root layout with `ObserveRoot` to measure Time to First Render automatically. Then, call `markInteractive` when your app is ready for user interaction to record Time to Interactive:

```tsx
import { ObserveRoot, useObserve } from 'expo-observe';
import { Stack } from 'expo-router';
import { useEffect } from 'react';

function RootLayout() {
  const { markInteractive } = useObserve();

  useEffect(() => {
    // Call markInteractive() after any initialization work behind the
    // splash screen completes.
    markInteractive();
  }, [markInteractive]);

  return <Stack />;
}

export default ObserveRoot.wrap(RootLayout);
```

For step-by-step instructions, including how to handle apps with multiple entry screens, see the [EAS Observe Get started guide](/eas/observe/get-started.md). To send custom events from your app, see [User-defined events](/eas/observe/events.md).

## API

```ts
import { Observe, ObserveRoot, useObserve } from 'expo-observe';
```

## Components

### `ObserveErrorBoundary`

Supported platforms: Android, iOS, tvOS.

Type: React.[Component](https://react.dev/reference/react/Component)<[AppMetricsErrorBoundaryProps](#appmetricserrorboundaryprops), State\>

A React error boundary that records render-phase errors as non-fatal `exception` log events (with the React component stack) and renders a `fallback` in place of the subtree that threw.

Render-phase errors don't reach `global.ErrorUtils`, so a boundary is the only way to capture them with the component stack. Place one around any subtree, or let `AppMetricsRoot` mount one via its `errorBoundaryFallback` prop.

### `ObserveInteractiveMarker`

Supported platforms: Android, iOS, tvOS.

Type: React.[Element](https://www.typescriptlang.org/docs/handbook/jsx.html#function-component)<[ObserveInteractiveMarkerProps](#observeinteractivemarkerprops)\>

Declarative wrapper around `useObserve().markInteractive(...)`. Renders nothing and calls `markInteractive` once when it first mounts, marking the moment the screen becomes interactive (used to compute the `tti` metric). Render it once the screen is ready for user interaction — for example, after its initial data has loaded.

Because `markInteractive` is only sent on mount, the marker is fire-once: changing `params` after the first render has no effect and warns in development. If you need to attach attributes that are only known later, call `useObserve().markInteractive(...)` imperatively instead.

Example

```tsx
import { ObserveInteractiveMarker } from 'expo-observe';

function Feed({ items }) {
  if (!items) return <Spinner />;
  return (
    <>
      <FeedList items={items} />
      <ObserveInteractiveMarker params={{ cacheHit: true }} />
    </>
  );
}
```

ObserveInteractiveMarkerProps

### `params`

Supported platforms: Android, iOS, tvOS.

Optional • Type: `MetricAttributes[params]`

Custom parameters attached to the TTI metric, forwarded to `markInteractive`. Values can be strings, numbers, booleans, or other JSON-serializable values.

### `ObserveRoot`

Supported platforms: Android, iOS, tvOS.

Type: React.[Element](https://www.typescriptlang.org/docs/handbook/jsx.html#function-component)<[AppMetricsRootProps](#appmetricsrootprops) & { children: [ReactNode](https://reactnative.dev/docs/react-node) }\>

## Static methods

### `getDerivedStateFromError(error)`

Supported platforms: Android, iOS, tvOS.

| Parameter | Type |
| --- | --- |
| `error` | `unknown` |

  

Returns: `State`

## Component methods

### `wrap(Component)`

Supported platforms: Android, iOS, tvOS.

| Parameter | Type |
| --- | --- |
| `Component` | `ComponentType<P>` |

  

Returns: `ComponentType`

## Hooks

### `useObserve()`

Supported platforms: Android, iOS, tvOS.

Returns: `{ markInteractive: (attributes: MetricAttributes) => void }`

## Interfaces

### `ExpoAppMetricsModuleType`

Supported platforms: Android, iOS, tvOS.

| Property | Type | Description |
| --- | --- | --- |
| NetworkRequestObserver | `NetworkRequestObserver` | Class for subscribing to HTTP requests observed by the native networking interceptor. Construct an instance to begin receiving `requestStarted`/`requestCompleted` events; release the instance (drop all references) to stop. |

ExpoAppMetricsModuleType Methods

### `clearStoredEntries()`

Supported platforms: Android, iOS, tvOS.

Returns: `Promise<void>`

### `logEvent(name, options)`

Supported platforms: Android, iOS, tvOS.

| Parameter | Type | Description |
| --- | --- | --- |
| `name` | `string` | Event name. Maps to the OpenTelemetry `event.name` attribute. |
| `options`(optional) | [LogEventOptions](#logeventoptions) | Optional body, attributes, and severity overrides. |

  

Records a log event against the current main session. The event is persisted locally and dispatched on the next `dispatchEvents()` flush as an OpenTelemetry log record sent to the `/v1/logs` endpoint.

Severity defaults to `"info"` when not provided.

Returns: `void`

### `markFirstRender()`

Supported platforms: Android, iOS, tvOS.

Returns: `void`

### `markInteractive(attributes)`

Supported platforms: Android, iOS, tvOS.

| Parameter | Type |
| --- | --- |
| `attributes`(optional) | [MetricAttributes](#metricattributes) |

  

Returns: `void`

### `setGlobalAttributes(attributes)`

Supported platforms: Android, iOS, tvOS.

| Parameter | Type |
| --- | --- |
| `attributes`(optional) | Record<string, [LogAttributeValue](#logattributevalue)\> | null |

  

Sets attributes merged into every subsequent metric and log event. Per-record keys win on collision. Pass `null`, `undefined`, or an empty object to clear.

Returns: `void`

Example

```ts
AppMetrics.setGlobalAttributes({
  subscription_tier: 'pro',
  experiment_variant: 'B',
});
```

### `ObserveIntegrationsConfig`

Supported platforms: Android, iOS, tvOS.

| Property | Type | Description |
| --- | --- | --- |
| expo-router(optional) | boolean | [ObserveNavigationIntegrationConfig](#observenavigationintegrationconfig) | Enables the `expo-router` integration, which records navigation metrics (`cold_ttr`, `warm_ttr`, `tti`) from router state changes. Requires `expo-router` to be installed. Pass an object to filter exported route/query params. Default: `false` |
| react-navigation(optional) | boolean | [ObserveNavigationIntegrationConfig](#observenavigationintegrationconfig) | Enables the `@react-navigation/native` integration, which records navigation metrics (`cold_ttr`, `warm_ttr`, `tti`). Requires `@react-navigation/native` to be installed and the app tree to be wrapped in `<ObserveNavigationContainer>` instead of the stock `<NavigationContainer>`. Pass an object to filter exported route/query params. Default: `false` |

### `ObserveModule`

Supported platforms: Android, iOS, tvOS.

Extends: [NativeModule](/versions/v57.0.0/sdk/expo.md#nativemoduletype)<[ObserveModuleEvents](#observemoduleevents)\>

ObserveModule Methods

### `configure(config)`

Supported platforms: Android, iOS, tvOS.

| Parameter | Type | Description |
| --- | --- | --- |
| `config` | [ObserveConfig](#observeconfig) | Observability settings to apply. |

  

Configures how observability events are collected and dispatched at runtime, such as the environment label, dispatching behavior, sampling, and integrations.

Returns: `void`

Example

```ts
import { Observe } from 'expo-observe';

Observe.configure({
  environment: 'production',
  dispatchingEnabled: true,
});
```

### `dispatchEvents()`

Supported platforms: Android, iOS, tvOS.

Dispatches pending events to the server immediately.

Events are dispatched automatically when the app moves to the background. On Android, a background worker dispatches events once network connectivity is available. On iOS, dispatching happens when the app resigns active state or is about to terminate. Call this method to flush events manually, for example, during testing or to ensure events are sent before a specific point.

Returns: `Promise<void>`

A promise that resolves when the pending events have been dispatched.

Example

```ts
import { Observe } from 'expo-observe';

await Observe.dispatchEvents();
```

### `getIntegrations()`

Supported platforms: Android, iOS, tvOS.

Returns the `integrations` config from the most recent `configure(...)` call, or an empty object if `configure` has not run yet.

Returns: `ObserveIntegrationsConfig`

### `logEvent(name, options)`

Supported platforms: Android, iOS, tvOS.

| Parameter | Type | Description |
| --- | --- | --- |
| `name` | `string` | Event name. |
| `options`(optional) | [LogEventOptions](#logeventoptions) | Optional body, attributes, and severity overrides. |

  

Records a log event against the current main session. The event is persisted locally and dispatched on the next `dispatchEvents()` flush.

Severity defaults to `"info"` when not provided.

Returns: `void`

### `markFirstRender()`

Supported platforms: Android, iOS, tvOS.

Marks the first render of the app. Used to compute the `cold_ttr` and `warm_ttr` metrics.

Returns: `void`

### `markInteractive(attributes)`

Supported platforms: Android, iOS, tvOS.

| Parameter | Type |
| --- | --- |
| `attributes`(optional) | [MetricAttributes](#metricattributes) |

  

Marks the moment the app becomes interactive. Used to compute the `tti` metric. Custom `routeName` and `params` can be attached via `attributes`.

> Note: When the `expo-router` or `@react-navigation/native` integration is active, prefer `useObserve().markInteractive(...)` — the hook fills in `routeName` from the current route, while this raw call does not.

Returns: `void`

### `registerIntegration(name, callback)`

Supported platforms: Android, iOS, tvOS.

| Parameter | Type | Description |
| --- | --- | --- |
| `name` | `K` | Integration name. |
| `callback` | `(config: ObserveIntegrationsConfig[K]) => void` | Function called with the integration configuration. |

  

Invokes a callback once when the named integration configuration becomes available.

Returns: `void`

Example

```ts
Observe.registerIntegration('expo-router', config => {
  console.log(config);
});
```

### `reportError(error)`

Supported platforms: Android, iOS, tvOS.

| Parameter | Type | Description |
| --- | --- | --- |
| `error` | `unknown` | The caught value. An `Error` is preferred, but any thrown value is accepted. |

  

Reports an error your code caught and handled, recorded as a non-fatal `exception` event. Use it to keep visibility into failures you recover from, which never reach the automatic global handler or an error boundary.

The thrown value is normalized: an `Error`'s `name`, `message`, and `stack` are captured; any other value (a string, a plain object) is stringified as the message.

Returns: `void`

Example

```ts
try {
  await syncCart();
} catch (error) {
  Observe.reportError(error);
}
```

### `setBundleDefaults(defaults)`

Supported platforms: Android, iOS, tvOS.

| Parameter | Type |
| --- | --- |
| `defaults` | `{ environment: string, isJsDev: boolean }` |

  

Pushes JS-bundle-derived facts (`process.env.NODE_ENV`, `__DEV__`) into native storage. Called automatically once when the package is first imported; should not be called by host apps directly.

Returns: `void`

### `setGlobalAttributes(attributes)`

Supported platforms: Android, iOS, tvOS.

| Parameter | Type |
| --- | --- |
| `attributes`(optional) | [ObserveAttributes](#observeattributes) | null |

  

Sets attributes merged into every subsequent metric and log event. Per-record keys win on collision. Pass `null`, `undefined`, or an empty object to clear.

Returns: `void`

Example

```ts
Observe.setGlobalAttributes({
  subscription_tier: 'pro',
  experiment_variant: 'B',
});
```

## Types

### `AppMetricsErrorBoundaryFallbackProps`

Supported platforms: Android, iOS, tvOS.

Arguments passed to a `fallback` render function.

| Property | Type | Description |
| --- | --- | --- |
| error | `unknown` | The value the subtree threw. Usually an `Error`, but any value can be thrown. |
| resetError | `() => void` | Clears the caught error and re-renders the children. Use it to offer a "try again" action; the children re-mount, so they run from a clean state. |

### `AppMetricsErrorBoundaryProps`

Supported platforms: Android, iOS, tvOS.

| Property | Type | Description |
| --- | --- | --- |
| children | `React.ReactNode` | - |
| fallback | React.ReactElement | null | (props: [AppMetricsErrorBoundaryFallbackProps](#appmetricserrorboundaryfallbackprops)) => React.ReactNode | Rendered in place of the subtree after an error is caught. Provide one of:
-   a React element to render as-is,
-   a function receiving the `error` and a `resetError` callback (to show details and offer retry),
-   `null` to render nothing.

. A boundary can't re-throw to reproduce React Native's default crash, so it always renders one of the above; there's no capture-only mode. Errors no boundary catches are still recorded by the global `ErrorUtils` handler. |

### `AppMetricsRootProps`

Supported platforms: Android, iOS, tvOS.

| Property | Type | Description |
| --- | --- | --- |
| children | `React.ReactNode` | - |
| errorBoundaryFallback(optional) | `AppMetricsErrorBoundaryProps[fallback]` | When set, the app is wrapped in an `AppMetricsErrorBoundary` with this `fallback`, capturing React render-phase errors at the root. Omit it and no boundary is mounted, so render errors keep React Native's default behavior (they're still recorded by the global `ErrorUtils` handler, just without the component stack). Pass `null` to capture but render nothing. To place a boundary deeper in the tree, use `AppMetricsErrorBoundary` directly. |

### `LogAttributeValue`

Supported platforms: Android, iOS, tvOS.

Value types accepted in a log event's `attributes` map. Strings, numbers, and booleans are stored as typed primitives; arrays and nested maps preserve their structure. Other JS values (functions, `Date`, `undefined`, etc.) are not supported and may be dropped by downstream consumers.

Type: `string` or `number` or `boolean` or `object` shaped as below:

| Property | Type | Description |
| --- | --- | --- |
| key[(index signature)](https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures) | [LogAttributeValue](#logattributevalue) | - |

### `LogEventOptions`

Supported platforms: Android, iOS, tvOS.

Optional configuration accepted by `logEvent`. The event name is passed as the first positional argument since it's required and the only field most callers set.

| Property | Type | Description |
| --- | --- | --- |
| attributes(optional) | Record<string, [LogAttributeValue](#logattributevalue)\> | null | Custom attributes attached to the event. Each entry is preserved with its original value type — see `LogAttributeValue` for the supported shapes. |
| body(optional) | `string | null` | Optional free-form message describing the event. |
| displayName(optional) | `string | null` | Optional human-friendly label for the event. Unlike `name` (a stable machine identifier), this is meant for display in dashboards and is not constrained to a naming scheme. |
| severity(optional) | [LogSeverity](#logseverity) | null | Severity of the event. Default: `"info"` |

### `LogSeverity`

Supported platforms: Android, iOS, tvOS.

Literal type: `string`

Severity of a log event, ordered from least to most severe:

-   `"trace"` — Fine-grained tracing, typically only useful while reproducing a specific issue.
-   `"debug"` — Diagnostic detail useful during development; usually filtered out in production.
-   `"info"` — Routine, expected events that record normal app behavior.
-   `"warn"` — Unexpected but recoverable conditions worth investigating.
-   `"error"` — An operation failed; the app continues running but is in a degraded state.
-   `"fatal"` — A severe failure, often immediately followed by app termination.

Acceptable values are: `'trace'` | `'debug'` | `'info'` | `'warn'` | `'error'` | `'fatal'`

### `MetricAttributes`

Supported platforms: Android, iOS, tvOS.

| Property | Type | Description |
| --- | --- | --- |
| params(optional) | `Record<string, unknown>` | Custom parameters to attach to the metric. |
| routeName(optional) | `string | null` | Name of the route associated with the metric. Some metrics populate this with a sensible default when omitted — for example, the TTI metric falls back to the initial route name detected from the router. |

### `ObserveAttribute`

Supported platforms: Android, iOS, tvOS.

Type: [LogAttributeValue](#logattributevalue)

Value types accepted as attribute values in `setGlobalAttributes` and the other Observe APIs. Strings, numbers, and booleans are stored as typed primitives; arrays and nested maps preserve their structure.

### `ObserveAttributes`

Supported platforms: Android, iOS, tvOS.

Type: Record<string, [ObserveAttribute](#observeattribute)\>

A map of attribute key to value, as accepted by `setGlobalAttributes` and other Observe APIs that take a free-form attributes payload.

### `ObserveConfig`

Supported platforms: Android, iOS, tvOS.

| Property | Type | Description |
| --- | --- | --- |
| dispatchInDebug(optional) | `boolean` | Whether to dispatch metrics that were collected in a debug build of the host app. When `false`, metrics produced by debug builds are marked as sent without being dispatched. When `true`, debug-build metrics are dispatched alongside release-build metrics. Has no effect on release builds. If `dispatchingEnabled` is `false` or this device is out-of-sample for `sampleRate`, nothing is dispatched regardless of `dispatchInDebug`. Default: `false` |
| dispatchingEnabled(optional) | `boolean` | Whether to dispatch observability events to the server. When `false`, any pending metrics are marked as sent without being dispatched and no further metrics are dispatched until this is set back to `true`. Default: `true` |
| environment(optional) | `string` | The environment for observability events. Default: `process.env.NODE_ENV` |
| integrations(optional) | [ObserveIntegrationsConfig](#observeintegrationsconfig) | Opt in to per-integration behavior. See the [Expo Router](/eas/observe/integrations/expo-router.md) and [React Navigation](/eas/observe/integrations/react-navigation.md) integrations, or [integrate your own package](/eas/observe/integrations/third-party.md). |
| sampleRate(optional) | `number` | Fraction of installations that should dispatch metrics, in `[0, 1]`. Values outside that range are clamped. The decision is **deterministic per installation** — a device is either permanently in-sample or out-of-sample for a given rate, so the choice is stable across app launches. Interaction with `dispatchingEnabled`:
-   If `dispatchingEnabled` is `false`, metrics are never dispatched
-   If `dispatchingEnabled` is `true` (or unset), metrics are dispatched only when this device is in-sample.

Note: Devices that end up out-of-sample drop pending metrics rather than accumulating them. Default: `undefined - metrics from all devices are sent` |

### `ObserveModuleEvents`

Supported platforms: Android, iOS, tvOS.

Events emitted by the native `ExpoObserve` module.

| Property | Type | Description |
| --- | --- | --- |
| configure | (payload: { integrations: [ObserveIntegrationsConfig](#observeintegrationsconfig) }) => void | Fired on every `configure(. .)` call, carrying the resolved `integrations` config |

### `ObserveNavigationIntegrationConfig`

Supported platforms: Android, iOS, tvOS.

| Property | Type | Description |
| --- | --- | --- |
| filteredParams(optional) | `string[]` | Route or query parameter keys to remove from exported navigation metric `routeParams`. When any configured parameter is removed from a metric, the exported resolved URL/path is replaced with `urlHidden: true`. Does not affect `routeName`. |
