Using PostHog

Edit page

A guide on installing and configuring PostHog for product analytics, session replay, and error tracking.

Android
iOS

PostHog is a product analytics platform with session replay, feature flags, and error tracking.

The EAS CLI integration automates the standard PostHog React Native setup: installing the SDK, creating a PostHog organization and project, and configuring your environment variables. You can also set it up manually and use the rest of this guide unchanged.

Prerequisites

3 requirements

1.

Expo account

Sign up for an Expo account.

2.

EAS CLI
Install EAS CLI globally with npm install -g eas-cli.

3.

Expo project linked to EAS

Create an Expo project and link it to EAS with eas init.

What you'll learn

This guide covers integrating PostHog with your Expo project:

Install and configure PostHog

1

Run the connect command

Run the following command in your project directory:

Terminal
eas integrations:posthog:connect

This command:

  • Prompts for a PostHog region (US or EU). The region sets your data residency and can't be changed after connecting.
  • Creates a PostHog organization and project for you, or reuses the existing one if you've already connected. (New PostHog accounts only; see Troubleshooting.)
  • Asks which features to set up: a multiselect of Analytics, Session replay, and Error tracking, all enabled by default.
  • If you enable error tracking, prompts you to paste a PostHog personal API key. Create one in PostHog under Settings → Personal API keys with the "Source map upload" preset. (Non-interactively, pass it with --posthog-cli-api-key.)
  • Installs the PostHog SDK and required Expo modules (plus the session-replay package if you keep that feature).
  • Adds the posthog-react-native/expo config plugin to your app config. The plugin wires up the native modules that PostHog's SDK, session replay, and error symbolication rely on. It edits a static app config file for you, but it can't edit a dynamic app config, so it prints the plugin entry for you to add.
  • Writes EXPO_PUBLIC_POSTHOG_API_KEY and EXPO_PUBLIC_POSTHOG_HOST to .env.local and to your EAS environment variables across Production, Preview, and Development environments. With error tracking enabled it also stores the personal API key as POSTHOG_CLI_API_KEY (with sensitive visibility) and the public POSTHOG_CLI_PROJECT_ID and POSTHOG_CLI_HOST.

Re-running connect is safe: it reuses your existing organization and project and prompts before overwriting environment variables.

Running in CI or non-interactively

Pass --non-interactive with --region US or --region EU (required, since there's no safe default for data residency). Control features with --session-replay / --no-session-replay and --error-tracking / --no-error-tracking; error tracking also needs --posthog-cli-api-key. Use --overwrite to replace existing environment variables without prompting.

2

Wrap your app in <PostHogProvider>

In your root layout file (src/app/_layout.tsx with Expo Router), wrap your app in <PostHogProvider>, reading the keys from the environment variables the command wrote. See the PostHog React Native docs for all options, including error-tracking autocapture.

src/app/_layout.tsx
import { PostHogProvider } from 'posthog-react-native'; import { Slot } from 'expo-router'; export default function RootLayout() { return ( <PostHogProvider apiKey={process.env.EXPO_PUBLIC_POSTHOG_API_KEY} options={{ host: process.env.EXPO_PUBLIC_POSTHOG_HOST, enableSessionReplay: false, // set to true if you enabled session replay // Capture JS exceptions (remove if you didn't enable error tracking): errorTracking: { autocapture: { uncaughtExceptions: true, unhandledRejections: true }, }, // disabled: __DEV__, // uncomment to stop sending events from development builds }}> <Slot /> </PostHogProvider> ); }

3

Create a development build

Session replay and native crash symbolication require a development build since they don't work in Expo Go. Product analytics works in Expo Go.

Terminal
eas build --profile development

4

Verify the configuration

Add a temporary button to capture a test event, run your development build, and tap it:

import { Button } from 'react-native'; import { usePostHog } from 'posthog-react-native'; // Inside a component const posthog = usePostHog(); <Button title="Send test event" onPress={() => posthog?.capture('test_event')} />

Open your PostHog project (at https://us.posthog.com or https://eu.posthog.com, depending on your region) and confirm the event arrives.

Error tracking

If you enabled error tracking, PostHog symbolicates two separate things, and you set them up independently:

  • JavaScript source maps for JS/TS exceptions, including Hermes bytecode. See Source maps.
  • Native debug symbols for native Android and iOS crashes (ProGuard/R8 mappings and dSYMs). Optional. See Native crash symbolication.

Source maps

Source maps make JavaScript stack traces (including Hermes bytecode) point to your original source instead of minified output.

First, set up injection so each bundle is tagged for upload. Add this to metro.config.js in your project root (create the file if it doesn't exist):

metro.config.js
const { getPostHogExpoConfig } = require('posthog-react-native/metro'); const config = getPostHogExpoConfig(__dirname); module.exports = config;

If you already customize your Metro config (for example, with NativeWind or in a monorepo), apply your changes to the config object that getPostHogExpoConfig returns instead of calling getDefaultConfig yourself.

PostHog's CLI authenticates with the POSTHOG_CLI_* environment variables that connect set. To upload from your own machine, run posthog-cli login instead.

On EAS Build, the posthog-react-native/expo config plugin uploads source maps automatically during the Android (Gradle) and iOS (Xcode) build phases, so there's no extra command to run.

On EAS Update, over-the-air updates ship only JavaScript, so upload just their source maps after each update (native symbols are fixed at build time). Install PostHog's CLI, then:

Terminal
eas update

posthog-cli hermes upload --directory dist

dist is the default EAS Update output directory.

Automate uploads with EAS Workflows

EAS Build uploads source maps as part of the native build, so a build workflow needs nothing extra. For updates, add a job that re-exports and uploads after publishing:

.eas/workflows/publish-update.yml
name: Publish update on: push: branches: ['main'] jobs: publish_update: name: Publish update type: update params: channel: production upload_update_sourcemaps: name: Upload OTA source maps to PostHog needs: [publish_update] environment: production steps: - uses: eas/checkout - uses: eas/install_node_modules - name: Export JS bundle and source maps run: npx expo export --dump-sourcemap --output-dir dist - name: Upload source maps to PostHog run: npx --yes @posthog/cli@latest hermes upload --directory dist

environment: production gives the job the POSTHOG_CLI_* variables that connect stored. The chunk IDs injected by getPostHogExpoConfig let the re-exported source maps match the bundle the update published. For external CI, set the POSTHOG_CLI_* variables yourself.

Native crash symbolication

Optional. Native crashes (as opposed to JavaScript exceptions) need native debug symbols uploaded at build time. The posthog-react-native/expo plugin can do this during EAS Build once you opt in:

app.json
{ "expo": { "plugins": [["posthog-react-native/expo", { "uploadNativeSymbols": true }]] } }

This is one of three pieces required end to end: build-time symbol upload (above), native crash autocapture in your provider, and the exception-autocapture setting in your PostHog project. See PostHog's native crash autocapture for the full setup.

Release tagging

Map each captured event back to a specific over-the-air update by registering super properties from expo-updates:

import { useEffect } from 'react'; import * as Updates from 'expo-updates'; import { usePostHog } from 'posthog-react-native'; function ReleaseTagger() { const posthog = usePostHog(); useEffect(() => { posthog?.register({ expo_update_id: Updates.updateId, expo_channel: Updates.channel, expo_runtime_version: Updates.runtimeVersion, }); }, [posthog]); return null; }

Render <ReleaseTagger /> inside <PostHogProvider>. Every subsequent capture() carries these properties, so you can filter or group events by expo_update_id in PostHog.

Feature flags

Once the provider is set up, feature flags work with no extra configuration. See PostHog's feature flags for React Native and bootstrapping to avoid a network round-trip on app start.

Manage the integration

Use these commands to manage the integration later:

Terminal
eas integrations:posthog:dashboard

eas integrations:posthog:disconnect

dashboard opens your linked PostHog project. disconnect removes the Expo-side link only. Your PostHog organization, project, and data are left intact.

Manual setup

connect is a shortcut for the standard PostHog React Native setup. To do it manually, follow PostHog's React Native installation guide, then set EXPO_PUBLIC_POSTHOG_API_KEY and EXPO_PUBLIC_POSTHOG_HOST (plus the POSTHOG_CLI_* variables for source maps). Wrap your app in <PostHogProvider> as shown in Step 2. For source maps, see Source maps.

Troubleshooting

No events arriving

Confirm EXPO_PUBLIC_POSTHOG_API_KEY is set in the environment profile your build uses. Note that disabled: __DEV__ stops events from development builds, so test in a preview or production build (or remove it temporarily). If a dev server was already running when connect wrote your environment variables, do a full reload (not Fast Refresh) so the app picks up the new EXPO_PUBLIC_* values.

Session replay isn't working

Session replay requires a development build since it doesn't work with Expo Go. If your project uses Continuous Native Generation, create one with npx expo run:android or npx expo run:ios locally, or with eas build --profile development.

Source maps aren't symbolicating

Confirm your metro.config.js is wrapped with getPostHogExpoConfig (see Source maps) and that the POSTHOG_CLI_* environment variables are set (connect adds them when error tracking is enabled). For over-the-air updates, make sure you ran posthog-cli hermes upload --directory dist after eas update.

'You already have a PostHog account'

The integration connects new PostHog accounts. If your email already has a PostHog account, set it up using the manual steps instead.

Learn more