Generated banner for Instrument Product Analytics
Generated logo for Instrument Product Analytics

Instrument Product Analytics

PostHogOfficialCreated by PostHog

  • Skill
  • Tool integration
  • Data and AI
  • MIT
  • Low risk

From the author

Add PostHog product analytics events to track user behavior. Use after implementing new features or reviewing PRs to ensure meaningful user actions are captured. Also handles initial PostHog SDK setup if not yet installed.
— the description by PostHog

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured, multi-step workflow to integrate PostHog product analytics into a wide variety of software projects. It guides the user through detecting the project's framework, installing the appropriate SDK, initializing the service, and strategically placing event capture calls. The skill emphasizes non-intrusive implementation, requiring that new analytics code be added alongside existing logic without altering the project's fundamental architecture. It also covers user identification, error tracking, and secure environment variable management.

What it does

  • Detects the project's framework and language to determine the correct PostHog SDK.
  • Handles initial SDK installation and initialization based on framework-specific patterns.
  • Identifies key user actions and business-critical events for tracking.
  • Implements event capture calls and user identification logic.
  • Configures environment variables securely for API tokens and host URLs.
  • Verifies the integration and performs targeted linting on modified files.

When to use it

  • After implementing new features that require tracking for product analytics.
  • When reviewing pull requests to ensure meaningful user behaviors are captured.
  • During the initial setup of a new project that needs product analytics.
  • When migrating or adding analytics to an existing application.

Advantages

  • Supports a broad range of frontend, backend, and mobile frameworks.
  • Ensures consistent event naming and property usage across the codebase.
  • Prioritizes non-breaking changes and minimal impact on existing code structure.
  • Automates the complex setup of environment variables and SDK initialization.

Usage examples

“Add PostHog analytics to my Next.js project.”
The agent will analyze the project structure, install the PostHog SDK, initialize it in the appropriate client/server files, and prompt you to identify key events to track.
“I need to track user signups and errors in my Django backend.”
The agent will locate your authentication logic, add identify calls, implement exception capture, and ensure the PostHog project tokens are managed via environment variables.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/posthog/instrument-product-analytics/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip instrument-product-analytics.zip -d .agents/skills

Documentation by PostHog

Add PostHog product analytics events

Use this skill to add product analytics events (capture calls) that track meaningful user actions in new or changed code. Use it after implementing features or reviewing PRs to ensure key user behaviors are captured. If PostHog is not yet installed, this skill also covers initial SDK setup. Supports any framework or language.

Supported frameworks and languages: Next.js, React Router, Nuxt, Vue, TanStack Start, SvelteKit, Astro, Angular, Django, Flask, FastAPI, Laravel, PHP, Ruby on Rails, Go, Elixir, Android, iOS, Flutter, React Native, Expo, and more.

Instructions

Follow these steps IN ORDER:

STEP 1: Analyze the codebase and detect the platform.

Look for dependency files (package.json, pubspec.yaml, Podfile, Package.swift, requirements.txt, Gemfile, composer.json, go.mod, mix.exs, etc.) to determine the framework and language.

Look for lockfiles (pnpm-lock.yaml, package-lock.json, yarn.lock, bun.lockb, go.sum, pubspec.lock, Podfile.lock, Package.resolved, mix.lock) to determine the package manager.

  • Check for existing PostHog setup. If PostHog is already installed and initialized, skip to STEP 5.

STEP 2: Research integration. (Skip if PostHog is already set up.) 2.1. Find the reference file below that matches the detected framework — it is the source of truth for SDK initialization, provider setup, and event capture patterns. Read it now. 2.2. If no reference matches, fall back to your general knowledge and web search. Use posthog.com/docs as the primary search source.

STEP 3: Install the PostHog SDK. (Skip if PostHog is already set up.)

  • Add the PostHog SDK package for the detected platform. Do not manually edit package.json — use the package manager's install command.

STEP 4: Initialize PostHog. (Skip if PostHog is already set up.)

  • Follow the framework reference for where and how to initialize. This varies significantly by framework (e.g., instrumentation-client.ts for Next.js 15.3+, AppConfig.ready() for Django, create_app() for Flask).

STEP 5: Plan event tracking.

  • From the project's file list, select between 10 and 15 files that might have interesting business value for event tracking, especially conversion and churn events.

  • Also look for files related to login that could be used for identifying users, along with error handling.

  • Find any existing posthog.capture() code. Make note of event name formatting. Don't duplicate existing events; supplement them.

  • Track actions only, not pageviews (those can be captured automatically). Exceptions can be made for "viewed"-type events at the top of a conversion funnel.

  • Server-side events are REQUIRED if the project includes any instrumentable server-side code (API routes, server actions, webhook handlers, payment/checkout completion, authentication endpoints).

STEP 6: Implement event capture.

  • For each planned event, add posthog.capture() calls with useful properties.

  • If a file already has existing integration code for other tools or services, don't overwrite or remove that code. Place PostHog code below it.

  • Do not alter the fundamental architecture of existing files. Make additions minimal and targeted.

  • You must read a file immediately before attempting to write it.

STEP 7: Identify users.

  • Add PostHog identify() calls on the client side during login and signup events. Use the contents of login and signup forms to identify users on submit.

  • If both frontend and backend exist, pass the client-side session and distinct ID using X-POSTHOG-DISTINCT-ID and X-POSTHOG-SESSION-ID headers to the server-side code. On the server side, make sure events have a matching distinct ID.

STEP 8: Add error tracking.

  • Add PostHog exception capture error tracking to relevant files, particularly around critical user flows and API boundaries.

STEP 9: Set up environment variables.

  • Check if the project already has PostHog environment variables configured (e.g. in .env, .env.local, or framework-specific env files). If valid values already exist, skip this step.

  • If the PostHog project token is missing, use the PostHog MCP server's projects-get tool to retrieve the project's api_token. If multiple projects are returned, ask the user which project to use. If the MCP server is not connected or not authenticated, ask the user for their PostHog project token instead.

  • For the PostHog host URL: check the projects-get MCP response for a region field — US maps to https://us.i.posthog.com, EU maps to https://eu.i.posthog.com. If the region is not available from the MCP response or from existing project configuration, ask the user: "Are you on PostHog US Cloud or EU Cloud?" Do not assume US Cloud.

  • Write these values to the appropriate env file using the framework's naming convention.

  • Reference these environment variables in code instead of hardcoding them.

STEP 10: Verify and clean up.

  • Check the project for errors. Look for type checking or build scripts in package.json.

  • Ensure any components created were actually used.

  • Run any linter or prettier-like scripts found in the package.json, but ONLY on the files you have edited or created during this session. Never run formatting or linting across the entire project's codebase.

Reference files

  • references/EXAMPLE-next-app-router.md - next-app-router example project code

  • references/EXAMPLE-next-pages-router.md - next-pages-router example project code

  • references/EXAMPLE-react-react-router-6.md - react-react-router-6 example project code

  • references/EXAMPLE-react-react-router-7-framework.md - react-react-router-7-framework example project code

  • references/EXAMPLE-react-react-router-7-data.md - react-react-router-7-data example project code

  • references/EXAMPLE-react-react-router-7-declarative.md - react-react-router-7-declarative example project code

  • references/EXAMPLE-nuxt-3-6.md - nuxt-3-6 example project code

  • references/EXAMPLE-nuxt-4.md - nuxt-4 example project code

  • references/EXAMPLE-vue-3.md - vue-3 example project code

  • references/EXAMPLE-react-tanstack-router-file-based.md - react-tanstack-router-file-based example project code

  • references/EXAMPLE-react-tanstack-router-code-based.md - react-tanstack-router-code-based example project code

  • references/EXAMPLE-tanstack-start.md - tanstack-start example project code

  • references/EXAMPLE-sveltekit.md - sveltekit example project code

  • references/EXAMPLE-astro-static.md - astro-static example project code

  • references/EXAMPLE-astro-view-transitions.md - astro-view-transitions example project code

  • references/EXAMPLE-astro-ssr.md - astro-ssr example project code

  • references/EXAMPLE-astro-hybrid.md - astro-hybrid example project code

  • references/EXAMPLE-angular.md - angular example project code

  • references/EXAMPLE-django.md - django example project code

  • references/EXAMPLE-flask.md - flask example project code

  • references/EXAMPLE-fastapi.md - fastapi example project code

  • references/EXAMPLE-python.md - python example project code

  • references/EXAMPLE-laravel.md - laravel example project code

  • references/EXAMPLE-php.md - php example project code

  • references/EXAMPLE-ruby-on-rails.md - ruby-on-rails example project code

  • references/EXAMPLE-ruby.md - ruby example project code

  • references/EXAMPLE-android.md - android example project code

  • references/EXAMPLE-swift.md - swift example project code

  • references/EXAMPLE-react-native.md - react-native example project code

  • references/EXAMPLE-expo.md - expo example project code

  • references/next-js.md - Next.js

  • references/react-router-v6.md - React router v6

  • references/react-router-v7-framework-mode.md - React router v7 framework mode (remix v3)

  • references/react-router-v7-data-mode.md - React router v7 data mode

  • references/react-router-v7-declarative-mode.md - React router v7 declarative mode

  • references/nuxt-js-3-6.md - Nuxt.js (v3.0 to v3.6)

  • references/nuxt-js.md - Nuxt.js

  • references/vue-js.md - Vue.js

  • references/tanstack-start.md - Tanstack start

  • references/svelte.md - Svelte

  • references/astro.md - Astro

  • references/angular.md - Angular

  • references/django.md - Django

  • references/flask.md - Flask

  • references/python.md - Python

  • references/posthog-python.md - PostHog python SDK

  • references/dotnet.md - .net

  • references/elixir.md - Elixir

  • references/go.md - Go

  • references/laravel.md - Laravel

  • references/php.md - Php

  • references/ruby-on-rails.md - Ruby on rails

  • references/ruby.md - Ruby

  • references/android.md - Android

  • references/ios.md - Ios

  • references/usage.md - Ios SDK usage

  • references/configuration.md - Ios SDK configuration

  • references/flutter.md - Flutter

  • references/react-native.md - React native

  • references/identify-users.md - Identify users

  • references/COMMANDMENTS.md - Framework-specific rules the integration must follow

Each framework reference contains SDK-specific installation, initialization, and usage patterns. Find the one matching the user's stack.

Key principles

  • Environment variables: Always use environment variables for PostHog keys. Never hardcode them.

  • Minimal changes: Add PostHog code alongside existing integrations. Don't replace or restructure existing code.

  • Match the docs: Follow the framework reference's initialization and capture patterns exactly.

Files

references/react-native.md

AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt

React Native

Canonical URL: https://posthog.com/docs/libraries/react-native

On this page

  • Install with the wizard

  • Installation

  • Capturing events

  • Autocapture

  • Common before_send patterns

  • Identifying users

  • Get the current user's distinct ID

  • Alias

  • Setting person properties

  • Super properties

  • Opt out of data capture

  • Flush

  • Reset after logout

  • Offline behavior

  • Opt in/out

  • Feature Flags

  • Experiments (A/B tests)

  • Group analytics

  • Error tracking

  • Logs

  • Session replay

  • Surveys

  • Push notifications

  • Debug mode

  • Disabling for local development

  • Upgrading from V1, V2 to V3 or V3 to V4

Install with the wizard

Run this command in your project directory to add PostHog to your React Native project. The wizard detects your framework, installs the SDK, and sets up your first events.

npx @posthog/wizard

Or set it up manually by following the rest of this guide.

Installation

Our React Native enables you to integrate PostHog with your React Native project. For React Native projects built with Expo, there are no mobile native dependencies outside of supported Expo packages.

To install, add the posthog-react-native package to your project as well as the required peer dependencies.

Expo apps
npx expo install posthog-react-native expo-file-system expo-application expo-device expo-localization
React Native apps
yarn add posthog-react-native @react-native-async-storage/async-storage react-native-device-info react-native-localize
# or
npm i -s posthog-react-native @react-native-async-storage/async-storage react-native-device-info react-native-localize
React Native Web and macOS

If you're using React Native Web or React Native macOS, do not use the expo-file-system package since the Web and macOS targets aren't supported, use the @react-native-async-storage/async-storage package instead.

Configuration
With the PosthogProvider

The recommended way to set up PostHog for React Native is to use the PostHogProvider. This utilizes the Context API to pass the PostHog client around, and enables autocapture.

To set up PostHogProvider, add it to your App.js or App.ts file:

App.js

// App.(js|ts)
import { usePostHog, PostHogProvider } from 'posthog-react-native'
...

export function MyApp() {
    return (
        <PostHogProvider apiKey="<ph_project_token>" options={{
            // usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com'
            host: 'https://us.i.posthog.com',
        }}>
            <MyComponent />
        </PostHogProvider>
    )
}

Then you can access PostHog using the usePostHog() hook:

const MyComponent = () => {
    const posthog = usePostHog()

    useEffect(() => {
        posthog.capture("event_name")
    }, [posthog])
}
Without the PosthogProvider

If you prefer not to use the provider, you can initialize PostHog in its own file and import the instance from there:

posthog.ts

import PostHog from 'posthog-react-native'

export const posthog = new PostHog('<ph_project_token>', {
  // usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com'
  host: 'https://us.i.posthog.com'
})

Then you can access PostHog by importing your instance:

import { posthog } from './posthog'

export function MyApp1() {
    useEffect(() => {
        posthog.capture('event_name')
    }, [])

    return <View>Your app code</View>
}

You can even use this instance with the PostHogProvider:

import { posthog } from './posthog'

export function MyApp() {
  return <PostHogProvider client={posthog}>{/* Your app code */}</PostHogProvider>
}
Choose an iOS dependency path for the native plugin

CocoaPods is becoming read-only. CocoaPods has announced the following schedule for its public specs repository:

  • November 2–6, 2026: A read-only dry run will prevent us from publishing new PostHog iOS SDK versions to CocoaPods.

  • December 2, 2026 onward: The repository will be permanently read-only. New PostHog iOS SDK versions will only be available through Swift Package Manager (SPM).

Existing iOS SDK versions will remain available through CocoaPods. To keep receiving iOS SDK updates, migrate the native plugin's posthog-ios dependency to SPM using the hybrid CocoaPods and SPM setup, or use full SPM integration if your app supports it. The hybrid path keeps CocoaPods for the plugin and other React Native dependencies.

This change does not affect @posthog/react-native-plugin releases on npm or Android.

The optional @posthog/react-native-plugin package adds native features such as session replay and native crash capture. Install it as described in the guide for the feature that you use. Then choose one iOS dependency path:

PathRequirementsWhat it resolves
CocoaPodsA React Native project that uses CocoaPodsCocoaPods resolves the plugin and posthog-ios. This remains the default path.
CocoaPods with posthog-ios through Swift Package ManagerReact Native 0.75 or later and a CocoaPods project with dynamic frameworksCocoaPods resolves the plugin. Swift Package Manager resolves posthog-ios.
Full Swift Package ManagerVerified with an iOS-only React Native 0.87.1 app and React Native Community CLI 20.2.0. Requires @posthog/react-native-plugin 2.4.0 or later, Xcode 16 or later, and an iOS 15.1 or later app deployment target.React Native's experimental Swift Package Manager integration resolves the plugin and posthog-ios. This path does not use CocoaPods.

This verification does not cover Expo or other React Native versions. Use CocoaPods or the hybrid path unless you validate the full Swift Package Manager path for your configuration.

CocoaPods

Use the standard React Native CocoaPods flow:

cd ios
pod install

The plugin podspec adds posthog-ios as a CocoaPods dependency. You do not need to add posthog-ios separately.

CocoaPods with posthog-ios through Swift Package Manager

Add the following property to ios/Podfile.properties.json:

{
  "posthog.useSpm": "true"
}

Add dynamic frameworks to your ios/Podfile:

use_frameworks! :linkage => :dynamic

Then install the pods:

cd ios
pod install

This setting changes only how the plugin resolves posthog-ios. The plugin and other React Native dependencies still use CocoaPods.

Full Swift Package Manager

This path uses React Native's experimental CocoaPods-free iOS integration. Every native dependency in your app must support React Native's full Swift Package Manager integration. Use CocoaPods or the hybrid path if a dependency does not support it.

Install your JavaScript dependencies first. Make a clean commit or a backup of your iOS project before the conversion. Then run this command from the ios directory:

npx react-native spm add --deintegrate --yes

The --deintegrate option removes the complete CocoaPods integration from the iOS project. React Native then finds the plugin's ios/Package.swift manifest. Swift Package Manager resolves the plugin and posthog-ios. Do not run pod install for this path.

PostHog CI verifies this path with the configuration in the requirements table. The verified app sets its deployment target to iOS 15.1. The plugin package manifest has a separate iOS 15 minimum. The CocoaPods and hybrid paths keep the plugin podspec's iOS 13 minimum.

Set up a reverse proxy (recommended)

We recommend setting up a reverse proxy, so that events are less likely to be intercepted by tracking blockers.

We have our own managed reverse proxy service, which is free for all PostHog Cloud users, routes through our infrastructure, and makes setting up your proxy easy.

If you don't want to use our managed service then there are several other options for creating a reverse proxy, including using Cloudflare, AWS Cloudfront, and Vercel.

Grouping products in one project (recommended)

If you have multiple customer-facing products (e.g. a marketing website + mobile app + web app), it's best to install PostHog on them all and group them in one project.

This makes it possible to track users across their entire journey (e.g. from visiting your marketing website to signing up for your product), or how they use your product across multiple platforms.

Add IPs to Firewall/WAF allowlists (recommended)

For certain features like heatmaps, your Web Application Firewall (WAF) may be blocking PostHog's requests to your site. Add these IP addresses to your WAF allowlist or rules to let PostHog access your site.

EU: 3.75.65.221, 18.197.246.42, 3.120.223.253

US: 44.205.89.55, 52.4.194.122, 44.208.188.173

These are public, stable IPs used by PostHog services.

PostHog captures heatmap screenshots using Browserless, which has its own IP addresses. Browserless publishes the current list here.

An allowlist does not help when your app has a private address. For apps on an internal network, see internal and intranet applications.

Configuration options

You can further customize how PostHog works through its configuration on initialization.

AttributeDescription
host Type: String Default: https://us.i.posthog.comPostHog API host (usually https://us.i.posthog.com by default or https://eu.i.posthog.com). Host is optional if you use https://us.i.posthog.com.
flushAt Type: Number Default: 20The number of events to queue before sending to PostHog (flushing).
flushInterval Type: Number Default: 10000The interval in milliseconds between periodic flushes.
maxBatchSize Type: Number Default: 100The maximum number of queued messages to be flushed as part of a single batch (must be higher than flushAt).
maxQueueSize Type: Number Default: 1000The maximum number of cached messages either in memory or on the local storage (must be higher than flushAt).
disabled Type: Boolean Default: falseIf set to true, the SDK is essentially disabled (useful for local environments where you don't want to track anything).
defaultOptIn Type: Boolean Default: trueIf set to false, the SDK will not track until the optIn() function is called.
sendFeatureFlagEvent Type: Boolean Default: trueWhether to track that getFeatureFlag was called (used by experiments).
preloadFeatureFlags Type: Boolean Default: trueWhether to load feature flags when initialized or not.
bootstrap Type: Object Default: {}Seeds identity (distinctId, isIdentifiedId) and feature flag state (featureFlags, featureFlagPayloads) during initialization. See SDK bootstrapping.
disableRemoteFeatureFlags Type: Boolean Default: falseWhen true, the SDK never fetches or evaluates feature flags from PostHog, and identify(), group(), and reset() stop triggering /flags requests. Supply flag values yourself via bootstrap (at startup) and updateFlags() (at runtime). Available in version 4.49.0+.
fetchRetryCount Type: Number Default: 3How many times HTTP requests will be retried.
fetchRetryDelay Type: Number Default: 3000The delay between HTTP request retries.
requestTimeout Type: Number Default: 10000Timeout in milliseconds for any calls.
featureFlagsRequestTimeoutMs Type: Number Default: 10000Timeout in milliseconds for feature flag calls.
sessionExpirationTimeSeconds Type: Number Default: 1800For session analysis, how long before a session expires (defaults to 30 minutes).
persistence Type: String Default: fileAllows you to provide the storage type. file will try to load the best available storage, the provided customStorage, customAsyncStorage, or in-memory storage.
customAppProperties Type: Object or Function Default: nullAllows you to provide your own implementation of the common information about your App or a function to modify the default App properties generated.
customStorage Type: Object Default: nullAllows you to provide a custom asynchronous storage such as async-storage, expo-file-system, or a synchronous storage such as mmkv. If not provided, PostHog will attempt to use the best available storage via optional peer dependencies. If persistence is set to memory, this option is ignored.
captureAppLifecycleEvents Type: Boolean Default: trueCaptures app lifecycle events such as Application Installed, Application Updated, Application Opened, Application Became Active, and Application Backgrounded. Enabled by default since version 4.39.0.
disableGeoip Type: Boolean Default: falseWhen true, disables automatic GeoIP resolution for events and feature flags.
enableSessionReplay Type: Boolean Default: falseEnable Recording of Session replay for Android and iOS.
sessionReplayConfig Type: Object Default: nullSession replay configuration. See the replay install docs for more details.
enablePersistSessionIdAcrossRestart Type: Boolean Default: falseWhen true, persists the $session_id across app restarts. If false, $session_id always resets on app restart.
evaluationContexts Type: Array of Strings Default: undefinedEvaluation context tags that constrain which feature flags are evaluated. When set, only flags with matching evaluation context tags (or no evaluation context tags) will be returned. This helps reduce unnecessary flag evaluations and improves performance. See evaluation contexts documentation for more details. Available in version 4.21.0+. The legacy parameter evaluationEnvironments (version 4.10.0+) is also supported for backward compatibility.
addTracingHeaders Type: Array of Strings Default: undefinedHostnames for which PostHog should add tracing headers to outgoing fetch requests. Matching requests include X-POSTHOG-DISTINCT-ID and X-POSTHOG-SESSION-ID, which lets backend events, errors, and LLM traces link back to frontend sessions and replays. Use hostnames only, without the protocol or path.
before_send Type: Function Default: undefinedA callback function that is called before each event is sent to PostHog. You can use it to modify, filter, or suppress events. Return null to drop the event, or return the modified event to send it. See customizing exception capture for details.
capturePushNotificationSubscriptions Type: Boolean Default: trueWhether to automatically register this device's push token so Workflows can target it. Requires @posthog/react-native-plugin. See push notifications. Available in version 4.62.0+.
capturePushNotificationOpened Type: Boolean Default: trueWhether to automatically capture $push_notification_opened when the user taps a push notification. Requires @posthog/react-native-plugin. See push notifications. Available in version 4.62.0+.
pushIdentityProvider Type: Function Default: undefinedSupplies a signed identity-verification token for push subscription requests. Only needed when your push channel requires identity verification. See identity verification. Available in version 4.62.0+.
Tracing headers

Use addTracingHeaders to connect React Native network requests to backend events, errors, and LLM traces captured by a server-side PostHog SDK:

const posthog = new PostHog('<ph_project_token>', {
  host: 'https://us.i.posthog.com',
  addTracingHeaders: ['api.example.com'],
})

Hostnames are matched exactly. The SDK patches global fetch and sends X-POSTHOG-DISTINCT-ID and X-POSTHOG-SESSION-ID on matching requests when those values are available.

Capturing events

You can send custom events using capture:

posthog.capture('user_signed_up')

Tip: We recommend using a [object] [verb] format for your event names, where [object] is the entity that the behavior relates to, and [verb] is the behavior itself. For example, project created, user signed up, or invite sent.

Setting event properties

Optionally, you can include additional information with the event by including a properties object:

posthog.capture('user_signed_up', {
    login_type: "email",
    is_free_trial: true
})
Capturing screen views
With @react-navigation/native and autocapture:

When using @react-navigation/native v6 or lower, screen tracking is automatically captured if the autocapture property is used in the PostHogProvider:

It is important that the PostHogProvider is configured as a child of the NavigationContainer:

// App.(js|ts)

import { PostHogProvider } from 'posthog-react-native'
import { NavigationContainer } from '@react-navigation/native'

export function App() {
    return (
        <NavigationContainer>
            <PostHogProvider apiKey="<ph_project_token>" autocapture>
                {/* Rest of app */}
            </PostHogProvider>
        </NavigationContainer>
    )
}

When using @react-navigation/native v7 or higher, screen tracking has to be manually captured:

// App.(js|ts)

import { PostHogProvider } from 'posthog-react-native'
import { NavigationContainer } from '@react-navigation/native'

// Using `PostHogProvider` is optional, but needed if you want to capture touch events automatically with the `captureTouches` option.
export function App() {
    return (
        <NavigationContainer>
            <PostHogProvider apiKey="<ph_project_token>" autocapture={{
              captureScreens: false, // Screen events are handled differently for v7 and higher
              captureTouches: true,
            }}>
                {/* Rest of app */}
            </PostHogProvider>
        </NavigationContainer>
    )
}

Check out and set it up the official way for Screen tracking for analytics.

Then call the screen method within the trackScreenView method.

const posthog = usePostHog() // use the usePostHog hook if using the PostHogProvider or your own custom posthog instance
// you can read the params from `getCurrentRoute()`
posthog.screen(currentRouteName, params)
With react-native-navigation and autocapture:

First, simplify the wrapping of your screens with a shared PostHogProvider:

import PostHog, { PostHogProvider } from 'posthog-react-native'
import { Navigation } from 'react-native-navigation';

export const posthog = new PostHog('<ph_project_token>');

export const SharedPostHogProvider = (props: any) => {
  return (
    <PostHogProvider client={posthog} autocapture={{
      captureScreens: false, // Screen events are handled differently for react-native-navigation
      captureTouches: true,
    }}>
      {props.children}
    </PostHogProvider>
  );
};

Then, every screen needs to be wrapped with this provider if you want to capture touches or use the usePostHog() hook

export const MyScreen = () => {
  return (
    <SharedPostHogProvider>
      <View>
        ...
      </View>
    </SharedPostHogProvider>
  );
};

Navigation.registerComponent('Screen', () => MyScreen);

Navigation.events().registerAppLaunchedListener(async () => {
  posthog.initReactNativeNavigation({
    navigation: {
      // (Optional) Set the name based on the route. Defaults to the route name.
      routeToName: (name, properties) => name,
      // (Optional) Tracks all passProps as properties. Defaults to undefined
      routeToProperties: (name, properties) => properties,
    },
    captureScreens: true,
  });
});
With expo-router:

Check out and set it up the official way for Screen tracking for analytics.

Then call the screen method within the useEffect callback.

const posthog = usePostHog() // use the usePostHog hook if using the PostHogProvider or your own custom posthog instance
posthog.screen(pathname, params)
Manually capturing screen capture events

If you prefer not to use autocapture, you can manually capture screen views by calling posthog.screen(). This function requires a name. You may also pass in an optional properties object.

posthog.screen('dashboard', {
    background: 'blue',
    hero: 'superhog',
})

Autocapture

PostHog autocapture can automatically track the following events for you:

  • Application Opened – when the app is opened from a closed state

  • Application Became Active – when the app comes to the foreground (e.g. from the app switcher)

  • Application Backgrounded – when the app is sent to the background by the user

  • Application Installed – when the app is installed.

  • Application Updated – when the app is updated.

  • $screen – when the user navigates (if using @react-navigation/native (v6 or lower) or react-native-navigation), check out the capturing screen views section

  • $autocapture – touch events when the user interacts with the screen

  • $exception – when the app throws exceptions.

⚠️ React Navigation v7 users

React Navigation v7 restricts navigation hooks (such as useNavigationState) to components rendered inside a Screen that belongs to a Navigator.

Because of this change, automatic screen tracking may throw errors if PostHog is initialized outside a screen context. This commonly affects apps upgrading from React Navigation v6 to v7.

For React Navigation v7, we recommend disabling automatic screen capture for screens and manually calling posthog.screen() inside each screen component. See the Capturing screen views section below.

Application lifecycle events are enabled by default. Screen capture is enabled by default in PostHogProvider unless you set captureScreens: false. Touch capture is disabled by default and requires captureTouches: true.

When touch capture is enabled, touch events for children of PostHogProvider are tracked, capturing a snapshot of the view hierarchy at that point. This enables you to create insights in PostHog without adding custom events.

PostHog will try to generate a sensible name for touched elements based on the React component displayName or name. If you prefer, you can set your own name using the ph-label prop:

<View ph-label="my-special-label"></View>
Autocapture configuration
<PostHogProvider apiKey="<ph_project_token>" autocapture={{
    captureTouches: true, // Disabled by default
    captureScreens: true, // Enabled by default
    ignoreLabels: [], // Any labels here will be ignored from the stack in touch events
    customLabelProp: "ph-label",
    maxElementsCaptured: 20,
    noCaptureProp: "ph-no-capture",
    propsToCapture: ["testID"], // Limit which props are captured. By default, identifiers and text content are captured.

    navigation: {
        // By default, only the screen name is tracked but it is possible to track the
        // params or modify the name by intercepting the autocapture like so
        routeToName: (name, params) => {
            if (params.id) return `${name}/${params.id}`
            return name
        },
        routeToProperties: (name, params) => {
            if (name === "SensitiveScreen") return undefined
            return params
        },
    },
}}>
    ...
</PostHogProvider>
Preventing sensitive data capture

If there are elements you don't want to be captured, you can add the ph-no-capture property. If this property is found anywhere in the view hierarchy, the entire touch event is ignored:

<View ph-no-capture>Sensitive view here</View>
Capturing screen views

With captureScreens: true (the default in PostHogProvider), PostHog captures a $screen event automatically when the user navigates, provided you're using @react-navigation/native (v6 or lower) or react-native-navigation.

To manually send a screen capture event, use the screen method:

posthog.screen('Dashboard', { fromIcon: 'bottom' })

React Navigation v7 users: automatic screen tracking may throw errors if PostHog is initialized outside a screen context. For v7, disable automatic screen capture (captureScreens: false) and call posthog.screen() manually inside each screen component.

Filtering autocaptured screens

You can stop specific screens from being autocaptured by filtering them in your before-send hook. Return null for any $screen event whose $screen_name matches a screen you don't want to track, and it's dropped before being sent – keeping unwanted screen views out of your event log.

Because it's just a function, you can filter however you like – an ignorelist (drop the screens you name), an allowlist (invert the check to capture only the screens you name), or any custom rule such as a name prefix, a regex, or a check against the event's properties.

before_send is a client option, so pass it via the provider's options prop (or to new PostHog(...) if you create the client yourself):

const IGNORED_SCREENS = new Set(['Splash', 'Debug'])

<PostHogProvider
    apiKey="<ph_project_token>"
    autocapture={{ captureScreens: true }}
    options={{
        host: 'https://us.i.posthog.com',
        before_send: (event) => {
            if (event?.event === '$screen') {
                const screenName = event.properties?.['$screen_name']
                return IGNORED_SCREENS.has(screenName) ? null : event
            }
            return event
        },
    }}
>
    {/* app */}
</PostHogProvider>

Swap the check for an allowlist (return TRACKED_SCREENS.has(screenName) ? event : null) if you'd rather capture only a specific set of screens.

Common before_send patterns

before_send accepts a single function or an array of functions that run in order, so you can compose several small hooks. Filtering screens is one use – here are a few others.

Drop a specific event. Stop an internal or debug event from ever being sent:

const posthog = new PostHog('<ph_project_token>', {
    before_send: (event) => {
        if (event?.event === 'debug_only_event') {
            return null // never send this event
        }
        return event
    },
})

Log events instead of sending them. Handy while debugging what would be captured:

const posthog = new PostHog('<ph_project_token>', {
    before_send: (event) => {
        console.log('[PostHog] would send', event?.event, event?.properties)
        return null // drop everything
    },
})

Redact sensitive properties. Strip a value before it leaves the device:

const posthog = new PostHog('<ph_project_token>', {
    before_send: (event) => {
        if (event?.properties?.email) {
            event.properties.email = '***'
        }
        return event
    },
})

For more examples, see the JavaScript Web SDK docs.

Identifying users

We highly recommend reading our section on Identifying users to better understand how to correctly use this method.

Using identify, you can associate events with specific users. This enables you to gain full insights as to how they're using your product across different sessions, devices, and platforms.

An identify call has the following arguments:

  • distinctId: Required. A unique identifier for your user. Typically either their email or database ID.

  • properties: Optional. A dictionary with key:value pairs to set the person properties

posthog.identify('distinctID',
  { // ($set):
      email: '[email protected]',
      name: 'My Name'
  }
)

$set_once works just like $set, except that it will only set the property if the user doesn't already have that property set. See the difference between $set and $set_once

posthog.identify('distinctID',
  {
    $set: {
        email: '[email protected]',
        name: 'My Name'
    },
    $set_once: {
        date_of_first_log_in: '2024-03-01'
    }
  }
)

You should call identify as soon as you're able to. Typically, this is after your user logs in. This ensures that events sent during your user's sessions are correctly associated with them.

When you call identify, all previously tracked anonymous events will be linked to the user.

Get the current user's distinct ID

You may find it helpful to get the current user's distinct ID. For example, to check whether you've already called identify for a user or not.

To do this, call posthog.get_distinct_id(). This returns either the ID automatically generated by PostHog or the ID that has been passed by a call to identify().

Alias

Sometimes, you want to assign multiple distinct IDs to a single user. This is helpful when your primary distinct ID is inaccessible. For example, if a distinct ID used on the frontend is not available in your backend.

In this case, you can use alias to assign another distinct ID to the same user.

// Sets alias for current user
posthog.alias('distinct_id')

We strongly recommend reading our docs on alias to best understand how to correctly use this method.

Setting person properties

Person properties enable you to capture, manage, and analyze specific data about a user. You can use them to create filters or cohorts, which can then be used in insights, feature flags, and more.

To set a user's properties, include the $set or $set_once property when capturing any event:

$set
posthog.capture('some_event', { $set: { userProperty: 'value' } })
$set_once

$set_once works just like $set, except it only sets the property if the user doesn't already have that property set.

posthog.capture('some_event', { $set_once: { userProperty: 'value' } })

You can also use setPersonProperties() and unsetPersonProperties() to manage person properties directly. See person properties for examples.

Super properties

Super properties are properties associated with events that are set once and then sent with every capture call, be it a $screen, an autocaptured touch, or anything else.

They are set using posthog.register, which takes a properties object as a parameter, and they persist across sessions.

For example:

posthog.register({
    'icecream pref': 'vanilla',
    team_id: 22,
})

The call above ensures that every event sent by the user will include "icecream pref": "vanilla" and "team_id": 22. This way, if you filtered events by property using icecream_pref = vanilla, it would display all events captured on that user after the posthog.register call, since they all include the specified Super Property.

This does not set the user's properties. This only sets the properties for their events. To store person properties, see the setting person properties section.

Removing stored super properties

Super Properties are persisted across sessions so you have to explicitly remove them if they are no longer relevant. In order to stop sending a Super Property with events, you can use posthog.unregister, like so:

posthog.unregister('icecream pref'),

This will remove the super property and subsequent events will not include it.

If you are doing this as part of a user logging out you can instead simply posthog.reset() which takes care of clearing all stored Super Properties and more.

Opt out of data capture

You can completely opt users out from data capture by default or on a per-person basis. See Opt in/out for the current React Native API.

Flush

You can configure how many events queue before flushing with flushAt. Setting this to 1 will send events immediately and will use more battery. The default is 20.

You can also configure the flush interval with flushInterval, in milliseconds (default 10000), after which queued events are sent regardless of how many have been gathered:

const posthog = new PostHog('<ph_project_token>', {
  flushAt: 20,
  flushInterval: 10000,
})

You can also manually flush the queue to start sending events immediately instead of waiting for the next batch:

await posthog.flush()

If a flush is already in progress, it returns a promise for the existing flush.

Flushing is best-effort and asynchronous – it starts sending queued events in the background but doesn't wait for the request to finish, so it isn't a delivery guarantee.

Reset after logout

To reset the user's ID and anonymous ID, call reset. Usually you would do this right after the user logs out.

posthog.reset()

Offline behavior

The PostHog React Native SDK will continue to capture events when the device is offline. When persistence is set to file (by default), the events are stored in a queue in the device's file storage. Even when the app is closed, the events are persisted and will be flushed when the app is opened again.

  • The queue has a maximum size defined by maxQueueSize in the configuration.

  • When the queue is full, the oldest event is deleted first.

  • The queue is flushed only when the device is online.

Opt in/out

By default, PostHog has tracking enabled unless it is forcefully disabled by default using the option { defaultOptIn: false }.

You can give your users the option to opt in or out by calling the relevant methods. Once these have been called they are persisted and will be respected until optIn/Out is called again or the reset function is called.

To opt in/out of tracking, use the following calls.

posthog.optedOut // See if a user has opted out
posthog.optIn() // opt in
posthog.optOut() // opt out

If you still wish capture these events but want to create a distinction between users and team in PostHog, you should look into Cohorts.

Feature Flags

PostHog's feature flags enable you to safely deploy and roll back new features as well as target specific users and groups with them.

There are two ways to implement feature flags in React Native:

  1. Using hooks.

  2. Loading the flag directly.

Method 1: Using hooks
Example 1: Boolean feature flags
import { useFeatureFlag } from 'posthog-react-native'

const MyComponent = () => {
    const booleanFlag = useFeatureFlag('key-for-your-boolean-flag')

    if (booleanFlag === undefined) {
        // the response is undefined if the flags are being loaded
        return null
    }

    // Optional use the 'useFeatureFlagWithPayload' hook for fetching the feature flag payload

    return booleanFlag ? <Text>Testing feature 😄</Text> : <Text>Not Testing feature 😢</Text>
}
Example 2: Multivariate feature flags
import { useFeatureFlag } from 'posthog-react-native'

const MyComponent = () => {
    const multiVariantFeature = useFeatureFlag('key-for-your-multivariate-flag')

    if (multiVariantFeature === undefined) {
        // the response is undefined if the flags are being loaded
        return null
    } else if (multiVariantFeature === 'variant-name') { // replace 'variant-name' with the name of your variant
      // Do something
    }

    // Optional use the 'useFeatureFlagWithPayload' hook for fetching the feature flag payload

    return <div/>
}
Method 2: Loading the flag directly
// Defaults to undefined if not loaded yet or if there was a problem loading
posthog.isFeatureEnabled('key-for-your-boolean-flag')

// Defaults to undefined if not loaded yet or if there was a problem loading
posthog.getFeatureFlag('key-for-your-boolean-flag')

// Multivariant feature flags are returned as a string
posthog.getFeatureFlag('key-for-your-multivariate-flag')

// Optional: fetch the payload (returns 'JsonType' or undefined if not loaded yet or if there was a problem loading)
posthog.getFeatureFlagResult('key-for-your-multivariate-flag')?.payload
Inspecting all feature flags

You can inspect all currently loaded feature flags with getAllFeatureFlags(). It returns each flag's key, enabled state, variant, and payload, and does not send a $feature_flag_called event, so calling it won't affect your experiment results or flag usage analytics:

for (const flag of posthog.getAllFeatureFlags()) {
    console.log(flag.key, flag.enabled, flag.variant, flag.payload)
}
Ensuring flags are loaded before usage

Every time a user opens the app, we send a request in the background to fetch the feature flags that apply to that user. We store those flags in the storage.

This means that for most screens, the feature flags are available immediately — except for the first time a user visits.

To handle this, you can use the onFeatureFlags callback to wait for the feature flag request to finish:

posthog.onFeatureFlags((flags) => {
  // feature flags are guaranteed to be available at this point
  if (posthog.isFeatureEnabled('flag-key')) {
    // do something
  }
})
Reloading flags

PostHog loads feature flags when instantiated and refreshes whenever methods are called that affect the flag.

If want to manually trigger a refresh, you can call reloadFeatureFlagsAsync():

posthog.reloadFeatureFlagsAsync().then((refreshedFlags) => console.log(refreshedFlags))

Or when you want to trigger the reload, but don't care about the result:

posthog.reloadFeatureFlags()
Feature flag caching

The React Native SDK caches feature flag values in AsyncStorage. Cached values persist indefinitely with no TTL until updated by a successful API call. This enables offline support and reduces latency, but means inactive users may see stale flag values from their last session.

For example, if a user last opened your app when a flag was false, that value remains cached even after you roll it out to 100%. When they reopen the app, the SDK returns the cached false first, then fetches the fresh true value from the API.

To ensure fresh flag values:

// Force refresh on app start
await posthog.reloadFeatureFlagsAsync()

Or clear cached values for inactive users:

if (lastActiveDate < migrationDate) {
  posthog.reset() // Clears all cached data
}
Request timeout

You can configure the featureFlagsRequestTimeoutMs parameter when initializing your PostHog client to set a flag request timeout. This helps prevent your code from being blocked in the case when PostHog's servers are too slow to respond. By default, this is set at 10 seconds.

export const posthog = new PostHog('<ph_project_token>', {
  // usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com'
  host: 'https://us.i.posthog.com',
  featureFlagsRequestTimeoutMs: 10000 // Time in milliseconds. Default is 10000 (10 seconds).
})
Error handling

When using the PostHog SDK, it's important to handle potential errors that may occur during feature flag operations. Here's an example of how to wrap PostHog SDK methods in an error handler:

function handleFeatureFlag(client, flagKey, distinctId) {
    try {
        const isEnabled = client.isFeatureEnabled(flagKey, distinctId);
        console.log(`Feature flag '${flagKey}' for user '${distinctId}' is ${isEnabled ? 'enabled' : 'disabled'}`);
        return isEnabled;
    } catch (error) {
        console.error(`Error fetching feature flag '${flagKey}': ${error.message}`);
        // Optionally, you can return a default value or throw the error
        // return false; // Default to disabled
        throw error;
    }
}

// Usage example
try {
    const flagEnabled = handleFeatureFlag(client, 'new-feature', 'user-123');
    if (flagEnabled) {
        // Implement new feature logic
    } else {
        // Implement old feature logic
    }
} catch (error) {
    // Handle the error at a higher level
    console.error('Feature flag check failed, using default behavior');
    // Implement fallback logic
}
Overriding server properties

Sometimes, you might want to evaluate feature flags using properties that haven't been ingested yet, or were set incorrectly earlier. You can do so by setting properties the flag depends on with these calls:

posthog.setPersonPropertiesForFlags({'property1': 'value', property2: 'value2'})

Note that these are set for the entire session. Successive calls are additive: all properties you set are combined together and sent for flag evaluation.

Whenever you set these properties, we also trigger a reload of feature flags to ensure we have the latest values. You can disable this by passing in the optional parameter for reloading:

posthog.setPersonPropertiesForFlags({'property1': 'value', property2: 'value2'}, false)

At any point, you can reset these properties by calling resetPersonPropertiesForFlags:

posthog.resetPersonPropertiesForFlags()

The same holds for group properties:

// set properties for a group
posthog.setGroupPropertiesForFlags({'company': {'property1': 'value', property2: 'value2'}})

// reset properties for all groups:
posthog.resetGroupPropertiesForFlags()

Note: You don't need to add the group names here, since these properties are automatically attached to the current group (set via posthog.group()). When you change the group, these properties are reset.

Automatic overrides

Whenever you call posthog.identify with person properties, we automatically add these properties to flag evaluation calls to help determine the correct flag values. The same is true for when you call posthog.group().

Default overridden properties

By default, we always override some properties based on the user IP address.

The list of properties that this overrides:

  1. $geoip_city_name

  2. $geoip_country_name

  3. $geoip_country_code

  4. $geoip_continent_name

  5. $geoip_continent_code

  6. $geoip_postal_code

  7. $geoip_time_zone

This enables any geolocation-based flags to work without manually setting these properties.

Bootstrapping flags

Since there is a delay between initializing PostHog and fetching feature flags, feature flags are not always available immediately. This makes them unusable if you want to do something like redirecting a user to a different page based on a feature flag.

To have your feature flags available immediately, you can initialize PostHog with precomputed values until it has had a chance to fetch them. This is called bootstrapping. After the SDK fetches feature flags from PostHog, it will use those flag values instead of bootstrapped ones.

Pass bootstrap in the initialization options to seed identity and flag values:

<PostHogProvider
    apiKey="<ph_project_token>"
    options={{
        host: 'https://us.i.posthog.com',
        bootstrap: {
            distinctId: 'distinct_id_of_your_user',
            isIdentifiedId: true,
            featureFlags: {
                'flag-1': true,
                'variant-flag': 'control',
            },
        },
    }}
>
    <MyComponent />
</PostHogProvider>

See bootstrapping Feature Flags for server-side evaluation and flag lifecycle, and SDK bootstrapping for cross-SDK identity behavior.

Supplying flags from your own backend

If you evaluate feature flags outside the SDK – for example on your own server with posthog-node local evaluation, then pass the results into your app – you can have the SDK use those values and never fetch flags itself.

Set disableRemoteFeatureFlags: true so the SDK never requests /flags (including the refetches that identify(), group(), and reset() normally trigger), then push your evaluated flags at runtime with updateFlags(flags, payloads?, { merge }):

const posthog = new PostHog('<ph_project_token>', {
  host: 'https://us.i.posthog.com',
  // Don't fetch or evaluate flags on-device – we supply them ourselves.
  disableRemoteFeatureFlags: true,
  // Optional: values that must be available at startup, before updateFlags() runs.
  // Without this, reads return their not-loaded defaults until you push flags.
  bootstrap: {
    featureFlags: { 'my-flag': true },
    featureFlagPayloads: { 'my-flag': { color: 'blue' } },
  },
})

// Later – e.g. after login, once your backend has evaluated flags for this user:
posthog.updateFlags(
  { 'my-flag': true, 'my-variant-flag': 'test' },
  { 'my-flag': { color: 'blue' } }
)

posthog.getFeatureFlag('my-variant-flag') // 'test'
posthog.getFeatureFlagResult('my-flag')?.payload // { color: 'blue' }

updateFlags replaces the stored flags by default; pass { merge: true } to merge into the existing set instead. Values persist across app restarts, and getFeatureFlag() / getFeatureFlagResult() read them back like any other flag.

Note that reset() (called on logout) clears the supplied flags, so re-push them with updateFlags() after the next identity change. Use bootstrap for any flag values that must be available at startup before updateFlags() runs.

Experiments (A/B tests)

Since experiments use feature flags, the code for running an experiment is very similar to the feature flags code. See adding experiment code for React Native examples.

It's also possible to run experiments without using feature flags.

Group analytics

Group analytics allows you to associate the events for that person's session with a group (e.g. teams, organizations, etc.). See Group Analytics for implementation details.

Note: This is a paid feature and is not available on the open-source or free cloud plan. Learn more on the pricing page.

Error tracking

To set up error tracking in your project, see the error tracking docs.

Native crash autocapture

JavaScript autocapture only covers exceptions thrown in your JS/TS code. To capture crashes in native code – a native module, the React Native runtime, or a C/C++ library – install the optional @posthog/react-native-plugin package and enable one or both of these options. What each option covers depends on the platform:

OptioniOSAndroid
nativeCrashesEvery native crash: uncaught Objective-C exceptions, Mach exceptions, and signals such as SIGSEGV and SIGABRT, including crashes in C/C++ codeJava/Kotlin crashes only
androidNdkCrashesIgnoredC/C++ (NDK) crashes, on Android 12 (API 31) or later. Requires posthog-react-native 4.78.0 and @posthog/react-native-plugin 2.12.0 or later

To capture every native crash on both platforms, enable both:

const posthog = new PostHog('<ph_project_token>', {
  host: 'https://us.i.posthog.com',
  errorTracking: {
    autocapture: {
      nativeCrashes: true,
      androidNdkCrashes: true,
    },
  },
})

Native capture is gated by your project's Enable exception autocapture setting, and crash reports need native debug symbols uploaded at build time to produce readable stack traces.

An Android NDK crash kills the process immediately, so PostHog captures it on the next app launch from the records the OS kept. Properties such as $app_version and the identity describe that launch, not the crash. The first launch after you enable androidNdkCrashes also captures NDK crashes the OS still holds from before.

Follow the React Native installation guide for the full setup, and native crash symbolication to upload symbols.

Error boundaries

You can use the PostHogErrorBoundary component to capture React rendering errors thrown by components:

import { PostHogProvider, PostHogErrorBoundary } from 'posthog-react-native'
import { View, Text } from 'react-native'

const App = () => {
  return (
    <PostHogProvider apiKey="<ph_project_token>">
      <PostHogErrorBoundary
        fallback={YourFallbackComponent}
        additionalProperties={{ screen: "home" }}
      >
        <YourApp />
      </PostHogErrorBoundary>
    </PostHogProvider>
  )
}

const YourFallbackComponent = ({ error, componentStack }) => {
  return (
    <View>
      <Text>Something went wrong!</Text>
      <Text>{error instanceof Error ? error.message : String(error)}</Text>
    </View>
  )
}

The fallback prop accepts a component to render when an error occurs. The additionalProperties prop lets you add custom properties to the captured error event.

Duplicate errors with console capture

If you have both PostHogErrorBoundary and console capture enabled in your errorTracking config, render errors will be captured twice. This is because React logs all errors to the console by default. To avoid this, set console: [] on errorTracking.autocapture (for example, errorTracking: { autocapture: { console: [] } }) when using PostHogErrorBoundary.

Customizing exception capture with before_send

You can use the before_send callback to modify, filter, or suppress exception events before they are sent to PostHog. This is useful for:

  • Adding custom properties to exceptions

  • Overriding exception fingerprints for custom grouping

  • Suppressing specific types of exceptions

  • Redacting sensitive information

const posthog = new PostHog('<ph_project_token>', {
  host: 'https://us.i.posthog.com',
  before_send: (event) => {
    if (event.event === '$exception') {
      const exceptionList = event.properties?.['$exception_list'] || []
      const exception = exceptionList.length > 0 ? exceptionList[0] : null

      if (exception) {
        // Add custom properties
        event.properties['custom_property'] = 'custom_value'

        // Override fingerprint for custom grouping
        event.properties['$exception_fingerprint'] = 'MyCustomGroup'
      }

      // Suppress specific exception types
      if (exception?.['$exception_type'] === 'IgnoredError') {
        return null // Drop the event
      }
    }
    return event
  },
})

You can also use before_send to sample or filter other event types. See the JavaScript Web SDK documentation for more examples.

Logs

To set up logs in your React Native app, follow the React Native logs installation guide. The SDK exposes posthog.captureLog, posthog.logger.{trace,debug,info,warn,error,fatal}, and posthog.flushLogs for sending structured records to PostHog Logs.

Minimum version: [email protected] or later.

Session replay

To set up session replay in your project, all you need to do is install the React Native SDK and the Session replay plugin, then follow the instructions to enable Session Replay for React Native.

Surveys

To set up surveys, follow the additional installation instructions for React Native. Surveys launched with popover presentation are automatically shown to users matching the display conditions you set up.

Note: URL and CSS selector targeting are not supported in React Native. Surveys that rely on these conditions will not appear.

Push notifications

The React Native SDK can register a device for Workflows push notifications and capture when a user opens one. For setup, including automatic and manual registration, capturing opens, opting out, and identity verification, see Push notifications.

Debug mode

If you're not seeing the expected events being captured, the feature flags being evaluated, or the surveys being shown, you can enable debug mode to see what's happening.

You can enable debug mode by setting the debug option to true in the PostHogProvider options. This will enable verbose logs about the inner workings of the SDK.

<PostHogProvider
    debug={true}
    apiKey="<ph_project_token>"
    options={{
        host: "https://us.i.posthog.com",
    }}
>

You can also call the debug() method in your code.

posthog.debug()

Disabling for local development

You may want to disable PostHog when working locally or in a test environment. You can do this by setting the disable option to true when initializing PostHog. Helpfully this allows you to continue using usePostHog and safely calling it without anything actually happening.

// App.(js|ts)
import { usePostHog, PostHogProvider } from 'posthog-react-native'
...

export function MyApp() {
    return (
        <PostHogProvider apiKey="<ph_project_token>" options={{
            // Disable PostHog in development (or whatever other logic you choose)
            disabled: __DEV__,
        }}>
            <MyComponent />
        </PostHogProvider>
    )
}

const MyComponent = () => {
    const posthog = usePostHog()

    useEffect(() => {
        // Safe to call even when disabled!
        posthog.capture("mycomponent_loaded", { foo: "bar" })
    }, [])
}

Upgrading from V1, V2 to V3 or V3 to V4

V1 of this library utilised the underlying posthog-ios and posthog-android SDKs to do most of the work. Since the new version is written entirely in JS, using only Expo supported libraries, there are some changes to the way PostHog is configured as well as actually calling PostHog.

For iOS, the new React Native SDK will attempt to migrate the previously persisted data (such as distinctId and anonymousId) which should result in no unexpected changes to tracked data.

For Android, it is unfortunately not possible for persisted Android data to be loaded which means stored information such as the randomly generated anonymousId or the distinctId set by posthog.identify will not be present. For identified users, the simple workaround is to ensure that identify is called at least once when the app loads. For anonymous users there is unfortunately no straightforward workaround they will show up as new anonymous users in PostHog.

Events such as Application Installed and Application Updated that require previously persisted data were unable to be migrated, the side effect being that you may see much higher numbers for Application Installed events. This is due to the fact that there is no native way of detecting a real "install" and as such, we store a marker the first time the SDK loads and treat that as an install.

// DEPRECATED V1 Setup

import PostHog from 'posthog-react-native'

await PostHog.setup('<ph_project_token>', {
    // usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com'
    host: 'https://us.i.posthog.com',
    captureApplicationLifecycleEvents: false, // Replaced by 'PostHogProvider'
    captureDeepLinks: false, // No longer supported
    recordScreenViews: false, // Replaced by 'PostHogProvider' supporting @react-navigation/native
    flushInterval: 30, // Stays the same
    flushAt: 20, // Stays the same
    android: {...}, // No longer needed
    iOS: {...}, // No longer needed
})

PostHog.capture("foo")


// V2 Setup difference
import PostHog from 'posthog-react-native'

const posthog = await Posthog.initAsync('<ph_project_token>', {
    // usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com'
    host: 'https://us.i.posthog.com',
    // Add any other options here.
})

// Use created instance rather than the PostHog class
posthog.capture("foo")

// V3 Setup difference
import PostHog from 'posthog-react-native'

const posthog = new PostHog('<ph_project_token>', {
    // usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com'
    host: 'https://us.i.posthog.com',
    // Add any other options here.
})

// Use created instance rather than the PostHog class
posthog.capture("foo")

// V4 Setup difference
import PostHog from 'posthog-react-native'

const posthog = new PostHog('<ph_project_token>', {
    // usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com'
    host: 'https://us.i.posthog.com',
    // captureAppLifecycleEvents is enabled by default since version 4.39.0 (previously named `captureNativeAppLifecycleEvents` or `autocapture={{ captureLifecycleEvents: true }}`)
    // captureMode: 'json', // No longer supported
    // maskPhotoLibraryImages: true, // No longer supported
})

posthog.setPersonPropertiesForFlags(...) // instead of `personProperties`
posthog.setGroupPropertiesForFlags(...) // instead of `groupProperties`

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an MCP server, Needs Python, Needs Node.js

Security

What our scanner noticed· Low risk

  • Instructions that use the network

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
PostHog
Publisher
PostHog (Official)
Source
PostHog/skills · skills/omnibus/instrument-product-analytics · commit a5d30e7
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by PostHog.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download instrument-product-analytics.zip