Phase
Get Started

Expo

llms.txt

Installation

Install the SDK

bun add phase-analytics

Install Required Peer Dependencies

Phase Analytics requires the following Expo packages to function properly:

bunx expo install @react-native-async-storage/async-storage expo-application expo-device expo-localization

These dependencies are required for the SDK to work correctly. The SDK uses:

  • @react-native-async-storage/async-storage - Local storage for the persistent offline queue
  • expo-application - App version collection (app_version)
  • expo-device - Device information collection
  • expo-localization - Locale and timezone detection

Optional packages:

bunx expo install @react-native-community/netinfo expo-router
  • @react-native-community/netinfo - Offline detection. Without it, the SDK keeps sending and relies on retries
  • expo-router - Required only for automatic screen tracking (trackNavigation)

Get Your API Key

  1. Sign in to Phase Dashboard
  2. Create a new project or select an existing one
  3. Open API Keys tab
  4. Copy your API Key (starts with phase_)

Setup

Wrap your app with the PhaseProvider component in your root layout to initialize the SDK:

React
app/_layout.tsx
import { PhaseProvider } from 'phase-analytics/expo';

export default function RootLayout() {
  return (
    <PhaseProvider
      apiKey="phase_xxx"
    >
      <Stack />
    </PhaseProvider>
  );
}

The PhaseProvider only initializes the SDK. Call Phase.identify() to register the device and start a session. Events tracked before identify() runs are buffered and sent afterwards.

Configuration

The PhaseProvider component accepts the following props:

Prop

Type

Usage

Identify User

Call Phase.identify() once at startup. This registers the device and starts a session. Calls made before PhaseProvider mounts (for example from child effects) are queued and run after initialization.

React
app/index.tsx
import { Phase } from 'phase-analytics/expo';
import { useEffect } from 'react';

export default function App() {
  useEffect(() => {
    // Initialize analytics - no PII collected by default
    Phase.identify();
  }, []);

  return <YourApp />;
}

Privacy by default:

  • No personal data is collected without explicit properties
  • Device ID is auto-generated and stored locally
  • Only technical metadata is collected (OS version, platform, locale)

Adding custom properties:

You can optionally attach user properties. Important: If you add PII (personally identifiable information), ensure you have proper user consent:

// After user login and consent
await Phase.identify({
  user_id: '123',
  plan: 'premium',
  beta_tester: true
});

// ⚠️ If adding PII, get consent first
const hasConsent = await customGetUserConsent();
if (hasConsent) {
  await Phase.identify({
    email: '[email protected]',
    name: 'John Doe'
  });
}

Properties must be primitives: string, number, boolean, or null.

Track Events

Track custom events with optional parameters. Events tracked before Phase.identify() runs are buffered in memory (up to 200 events, for up to one hour) and sent once the session exists. If more than 200 pile up, the oldest are dropped.

// Event without parameters
Phase.track('app_opened');

// Event with parameters
Phase.track('purchase_completed', {
  amount: 99.99,
  currency: 'USD',
  product_id: 'premium_plan'
});

Event naming rules:

  • Alphanumeric characters, underscores (_), hyphens (-), periods (.), forward slashes (/), and spaces
  • 1-256 characters
  • Examples: purchase, user.signup, payment/success, Button Clicked

Event parameters:

  • Flat primitive object only
  • Values must be string, number, boolean, or null
  • Max 32 keys
  • Key max 32 characters
  • String value max 256 characters
  • Serialized payload max 8 KB
  • Empty objects are normalized away

Automatic Screen Tracking

Screen tracking is off by default. Install expo-router and set trackNavigation to true in your root layout. The SDK uses Expo Router's navigation hooks to track screen changes automatically:

React
app/_layout.tsx
import { PhaseProvider } from 'phase-analytics/expo';
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <PhaseProvider
      apiKey="phase_xxx"
      trackNavigation={true}
    >
      <Stack />
    </PhaseProvider>
  );
}

How it works:

  • Wraps any Expo Router component (Stack, Tabs, Drawer, etc.)
  • Automatically tracks screen views on route changes
  • Screen names are derived from pathname
  • Works with nested routes and dynamic segments

If expo-router is not installed, trackNavigation logs an error and no screens are tracked.

Manual Screen Tracking

Use Phase.trackScreen() when trackNavigation is off or you use a custom navigator. params follow the same rules as Phase.track().

Phase.trackScreen('/settings/profile');

Phase.trackScreen('/settings/profile', { tab: 'billing' });

Flush

Events are sent automatically in batches (see Delivery), so you rarely need this. Phase.flush() sends queued data right away and resolves when the attempt finishes.

await Phase.flush();

Reset

Use Phase.reset() to forget the current device, for example on logout. It ends the session and generates a new device ID. Data already queued for the previous device is still delivered. Call Phase.identify() again to start tracking.

await Phase.reset();
await Phase.identify();

Type Reference

DeviceProperties

Custom user/device attributes passed to Phase.identify():

Prop

Type

EventParams

Event parameters passed to Phase.track() and Phase.trackScreen():

Prop

Type

How It Works

Delivery

Devices, sessions, events and heartbeats go through a persistent queue and are sent to the batch endpoint:

  • Every ~10 seconds while data is queued, as soon as 20 items are queued, and when the app goes to the background
  • Items leave the queue only after the server accepts them. Failed requests are retried with backoff and honor Retry-After
  • Changing the API key clears data queued under the previous key

Offline Support

The queue survives app restarts. It is stored with @react-native-async-storage/async-storage and capped at ~1.5 MB, dropping the oldest sessions first when full.

With @react-native-community/netinfo installed, nothing is sent while the device is offline and only the latest heartbeat per session is kept. Queued data is sent when the connection is restored.

Privacy

  • No personal data is collected by default
  • Device IDs are generated locally and stored persistently
  • Geolocation is resolved server-side from IP address (disable with properties)
  • All data collection is optional via configuration

Performance

  • Queued data is batched and sent asynchronously
  • Failed requests retry with exponential backoff
  • Maximum batch size: 50 items per request
  • Offline queue capped at 1000 items / ~1.5 MB