Using PostHog
Edit page
A guide on installing and configuring PostHog for product analytics, session replay, and error tracking.
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.
3 requirements
3 requirements
1.
Sign up for an Expo account.
2.
npm install -g eas-cli.3.
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 in your React Native app
- Error tracking with source maps and native crash symbolication
- Release tagging, feature flags, and troubleshooting
Install and configure PostHog
1
Run the connect command
Run the following command in your project directory:
- eas integrations:posthog:connectThis 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/expoconfig 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_KEYandEXPO_PUBLIC_POSTHOG_HOSTto .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 asPOSTHOG_CLI_API_KEY(with sensitive visibility) and the publicPOSTHOG_CLI_PROJECT_IDandPOSTHOG_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.
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.
- eas build --profile development4
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):
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:
- eas update- posthog-cli hermes upload --directory distdist 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:
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:
{ "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:
- eas integrations:posthog:dashboard- eas integrations:posthog:disconnectdashboard 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.