Source maps upload on EAS Build vs EAS Update
On EAS Build, the posthog-react-native/expo config plugin uploads source maps automatically during Android (Gradle) and iOS (Xcode) build phases. On EAS Update, over-the-air updates ship only JavaScript, so upload just their source maps after each update: run `eas update --platform <platform>` then `posthog-cli hermes upload --directory dist`. Export one platform per upload since PostHog uploads native (Hermes) source maps. The dist directory is the default EAS Update output directory.
Native crash symbolication plugin configuration
To enable native debug symbol upload at build time during EAS Build, add to app.json: { "expo": { "plugins": [["posthog-react-native/expo", { "uploadNativeSymbols": true }]] } }. This is one of three required pieces: build-time symbol upload, native crash autocapture in the provider, and exception-autocapture setting in your PostHog project.
Release tagging with expo-updates super properties
Map captured events to specific over-the-air updates by registering super properties from expo-updates. Example: import usePostHog from posthog-react-native and Updates from expo-updates. In a component, call posthog.register({ expo_update_id: Updates.updateId, expo_channel: Updates.channel, expo_runtime_version: Updates.runtimeVersion }). Render this component inside PostHogProvider. Every subsequent capture() carries these properties for filtering or grouping events by expo_update_id in PostHog.
eas integrations:posthog:connect command and automation
The `eas integrations:posthog:connect` command automates PostHog setup for React Native projects. It prompts for a PostHog region (US or EU) which sets data residency and cannot be changed after connecting. The command creates a PostHog organization and project, or reuses an existing one. It prompts to select features: Analytics, Session replay, and Error tracking (all enabled by default). If error tracking is enabled, it prompts for a PostHog personal API key created under Settings → Personal API keys with the 'Source map upload' preset. The command installs the PostHog SDK and required Expo modules, adds the posthog-react-native/expo config plugin to the app config, and writes EXPO_PUBLIC_POSTHOG_API_KEY and EXPO_PUBLIC_POSTHOG_HOST to .env.local and EAS environment variables across Production, Preview, and Development environments. With error tracking enabled, it also stores POSTHOG_CLI_API_KEY (sensitive visibility), POSTHOG_CLI_PROJECT_ID, and POSTHOG_CLI_HOST. Re-running connect is safe and reuses existing setup.
eas integrations:posthog management commands
Use `eas integrations:posthog:dashboard` to open your linked PostHog project. Use `eas integrations:posthog:disconnect` to remove the Expo-side link only; your PostHog organization, project, and data remain intact.
PostHog prerequisites for Expo integration
Prerequisites: Expo account (sign up at expo.dev/signup), EAS CLI installed globally with `npm install -g eas-cli`, and an Expo project linked to EAS via `eas init`.
PostHog 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 running when connect wrote environment variables, do a full reload (not Fast Refresh) so the app picks up the new EXPO_PUBLIC_* values.
Test event capture in PostHog
To verify PostHog configuration, add a temporary button that captures a test event using usePostHog hook: 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.
eas integrations:posthog:connect non-interactive mode
Pass `--non-interactive` with `--region US` or `--region EU` (required, no safe default for data residency). Control features with `--session-replay` / `--no-session-replay` and `--error-tracking` / `--no-error-tracking`. Error tracking also requires `--posthog-cli-api-key`. Use `--overwrite` to replace existing environment variables without prompting.
PostHogProvider setup with Expo Router
Wrap your app in PostHogProvider in the root layout file (src/app/_layout.tsx with Expo Router), reading keys from environment variables. Example: wrap Slot with PostHogProvider passing apiKey={process.env.EXPO_PUBLIC_POSTHOG_API_KEY} and options object containing host, enableSessionReplay, and optional errorTracking.autocapture settings.
Development build requirement for session replay and crash symbolication
Session replay and native crash symbolication require a development build and do not work in Expo Go. Product analytics works in Expo Go. Run `eas build --profile development` to create a development build.
Token management for multiple notification providers
Track both Expo push tokens and native device tokens in your database. This provides flexibility for future integrations, especially with marketing tools that send notifications directly via FCM or APNs.
Best practice: avoid mixing client-side notification implementations
Different notification services may have conflicting client-side implementations. Use a consistent approach to prevent potential issues.
React Native Firebase messaging for push notifications
React Native Firebase provides a messaging module that lets you use Firebase Cloud Messaging (FCM) as a unified push notification service for both Android and iOS. Although FCM is often associated with Android, it also supports iOS by routing messages through Apple Push Notification service (APNs). iOS notifications still go through APNs, and Firebase automatically manages this routing behind the scenes.
CleverTap Expo integration
CleverTap is an all-in-one customer engagement platform that delivers personalized, real-time, omnichannel messaging across push notifications, in-app messages, email, and more. It offers advanced segmentation, analytics, and campaign automation. The CleverTap React Native SDK and Expo config plugin make it easy to integrate CleverTap into Expo projects. The config plugin handles all native module setup during the prebuild process, allowing you to configure CleverTap through your app config without manually modifying native code.
Customer.io Expo integration
Customer.io is a customer engagement platform that allows you to design automated workflows utilizing push notifications, in-app messaging, email, SMS capabilities, and more. It supports device-side metrics collection for customizing push notifications tailored to user behaviors and preferences. Customer.io provides an Expo plugin for direct integration with Expo projects.
Accessing native capabilities via Expo Modules API and config plugins
Expo apps can work with any notification service or any of the notification capabilities offered by the Android and iOS operating systems. Even if a package doesn't yet exist for a feature, native code can be written to access it via the Expo Modules API, and native project configurations can be automated using config plugins.
Braze Expo integration
Braze is a customer engagement platform that delivers personalized, cross-channel messaging through push notifications, in-app messaging, email, SMS, and web. It supports rich notification content, push notification campaigns, and support for resending notifications after failed deliveries on Android. Braze provides a React Native SDK and a config plugin for Expo integration.
OneSignal Expo integration
OneSignal is a customer engagement platform that provides push notifications, in-app messaging, SMS, and email services. It supports rich media in notifications and engagement analytics, and includes an Expo config plugin for direct integration into Expo projects.
Supabase client configuration example
```ts utils/supabase.ts
import 'expo-sqlite/localStorage/install';
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL!;
const supabasePublishableKey = YOUR_REACT_NATIVE_SUPABASE_PUBLISHABLE_KEY!;
export const supabase = createClient(supabaseUrl, supabasePublishableKey, {
auth: {
storage: localStorage,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false,
},
});
```
This example shows how to set up a Supabase client for use throughout an Expo React Native application.
Supabase installation with Expo
Install Supabase TypeScript SDK and sqlite dependency for an Expo project using: npx expo install @supabase/supabase-js expo-sqlite
Supabase client initialization in Expo
Create a Supabase client helper file by importing 'expo-sqlite/localStorage/install' and calling createClient(supabaseUrl, supabasePublishableKey) with auth options. The auth configuration should include storage set to localStorage, autoRefreshToken set to true, persistSession set to true, and detectSessionInUrl set to false.
Supabase Row Level Security allows direct database access from React Native
Supabase uses Row Level Security (RLS) to secure data, which makes it possible to directly interact with the Supabase Postgres database from a React Native application without needing a server in between.
Supabase provides REST and GraphQL APIs
Supabase automatically generates a REST API from your database. It also exposes a GraphQL API that allows you to use GraphQL clients like Apollo Client to query your database.
Supabase TypeScript SDK features
The supabase-js TypeScript client library combines all Supabase services including database, authentication, realtime syncing, storage, and edge functions in one convenient package.
Supabase Publishable key is safe to expose in Expo apps
The Supabase Publishable key can be safely exposed in your Expo app code because Supabase has Row Level Security enabled in the database to protect your data.
Sentry setup with Expo wizard command
Run `npx @sentry/wizard@latest -i reactNative` (or with yarn dlx, pnpm dlx, bunx) in your project directory to automatically install dependencies, configure Sentry, set up Metro configuration, and add initialization code to your app.
Sentry account credentials needed for Expo
To integrate Sentry with Expo, you need: organization slug (from Organization settings tab), project name (from project Settings > Projects tab), DSN (from project Settings > Projects > Project name > SDK Setup > Client Keys (DSN) tab), and an Organization Auth Token (from Developer Settings > Auth Tokens, which is automatically scoped for Source Map Upload and Release Creation).
Sentry-Expo integration installation in EAS dashboard
Sentry owner, manager, or admin permissions are required. Log in to your Expo account and open Account settings > Overview. Under Connections, click Connect next to Sentry. Log in to your Sentry account and accept the integration into your organization.
Link Sentry project to EAS project
After connecting Sentry and Expo accounts, link your EAS Project to your Sentry Project by opening Projects > [Your Project] > Configuration > Project settings in EAS, clicking Link, and selecting your Sentry Project from the dropdown.
View Sentry data in EAS dashboard
To see Sentry data in the EAS dashboard, open Projects > [Your Project] > Updates > Deployments > [Deployment] to view Sentry data from a Release. You can view crash reports, access session replays, get detailed stack traces with full context, and navigate between EAS and Sentry for debugging.
Test Sentry integration with error button
Verify Sentry configuration by creating a new release build and adding a test button to your app that throws an error, for example: `<Button title="Press me" onPress={() => { throw new Error('Hello, again, Sentry!'); }}/>`. This confirms that sourcemaps are wired up correctly.
Resend integration overview
Resend is an email API platform designed for developers that allows you to send, receive, and manage emails programmatically through an API. It can be used to send transactional emails for use cases like newsletters and marketing emails. The API also allows you to set up webhooks for email events, manage domains for deliverability, and receive emails via webhooks.
Resend with Expo Router API Routes setup requirements
To integrate Resend with Expo and React Native using Expo Router, you need a project using Expo Router, an Expo account, EAS CLI installed globally, and a Resend account.
Example Resend API route implementation
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function POST(request: Request) {
const body = await request.json();
const { email } = body;
if (!email) {
return Response.json({ success: false });
}
await resend.contacts.create({
email: email,
// Provide dynamic values on your own
firstName: 'Steve',
lastName: 'Wozniak',
unsubscribed: false,
});
return Response.json({ success: true });
}
This example shows a POST endpoint that extracts an email from the request body and creates a contact in Resend using the Resend SDK.
Environment variables for base URL configuration
Add base URL environment variables to .env.local to make the API route accessible from your Expo app. Use EXPO_PUBLIC_BASE_URL for the deployed URL via EAS Hosting (e.g., https://example-resend.expo.app) and EXPO_PUBLIC_BASE_URL_LOCAL for local testing (e.g., http://localhost:8081). Only variables prefixed with EXPO_PUBLIC_ can be used in frontend code.
Example form for email submission in Expo app
import { useRef, useState } from 'react';
import { Alert, Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
export default function Index() {
const [email, setEmail] = useState('');
const inputRef = useRef<TextInput>(null);
const handleSubmit = async () => {
if (!email) {
alert('Email is required.');
return;
}
if (inputRef.current) {
inputRef.current.blur();
}
try {
const response = await fetch(
`${process.env.EXPO_PUBLIC_BASE_URL_LOCAL}/api/audience`,
{
method: 'POST',
body: JSON.stringify({ email }),
}
);
await response.json();
Alert.alert('Success', 'Email sent successfully.', [
{
text: 'Continue',
},
]);
} catch (error) {
alert('Something went wrong.');
console.error(error);
}
};
return (
<View style={styles.container}>
<TextInput
placeholder="Email"
value={email}
onChangeText={setEmail}
ref={inputRef}
autoCapitalize="none"
keyboardType="email-address"
style={styles.input}
/>
<Pressable style={styles.button} onPress={handleSubmit}>
<Text style={styles.buttonText}>Send email</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
input: {
borderWidth: 1,
borderColor: 'gray',
padding: 10,
width: '60%',
height: '6%',
borderRadius: 10,
marginBottom: 10,
margin: 20,
},
button: {
padding: 10,
backgroundColor: '#000000',
borderRadius: 10,
},
buttonText: {
color: 'white',
textAlign: 'center',
},
});
This example shows a form component that collects an email address and submits it to the /api/audience endpoint.
Deploy API route to EAS Hosting
To deploy the API route to EAS Hosting, run `npx expo export --platform web` to export web and API assets to a dist directory, then run `eas deploy --prod`. The eas deploy --prod command automatically creates an EAS project if needed and prompts you to choose the preview URL, which should match the EXPO_PUBLIC_BASE_URL in .env.local.
Switch base URL after deploying to EAS Hosting
When deploying to EAS Hosting, update the frontend code to use EXPO_PUBLIC_BASE_URL instead of EXPO_PUBLIC_BASE_URL_LOCAL in the fetch request. The EXPO_PUBLIC_BASE_URL should be set to the deployed URL matching the preview URL you chose during the eas deploy --prod process.
Store Resend API key in .env.local
After generating an API key from the Resend dashboard at resend.com/api-keys, save it to a .env.local file in your Expo project with the key RESEND_API_KEY. Do not commit the .env.local file to version control; add it to .gitignore since the API key is sensitive.
Install Resend SDK in Expo project
Install the Resend SDK using the command `npx expo install resend`. The resend SDK is a server-only library that allows you to send emails from the server-side code of your app, typically used with API Routes.
Enable API Routes with web.output setting
To enable API Routes in your Expo project, set web.output to server in the app config file (app.json): {"web": {"output": "server"}}
Create API route file with +api.ts extension
API route files must use the +api.ts extension (e.g., api/audience+api.ts). Create these files inside the src/app directory. The +api.ts extension tells Expo Router to identify the file as an API Route.
Vexo initialization code example
Initialize Vexo by adding the following code in your app's entry file (index.js, App.js, or src/app/_layout.tsx if using Expo Router): import { vexo } from 'vexo-analytics'; vexo('YOUR_API_KEY');. You may want to wrap this with if (!__DEV__) { ... } to only run Vexo in production.
Vexo compatibility with Expo
Vexo is compatible with Expo Development builds and does not require additional configuration plugins. Vexo is not supported with Expo Go, as it requires custom native code.
Vexo dashboard features
Vexo provides a complete dashboard with metrics including Active Users, Session Time, Downloads, OS Distribution, Version Adoption, Geographic Insights, and Popular Screens. It also supports Session Replays (watch real user sessions), Heatmaps (identify most engaged areas), Funnels (analyze user flows and conversion rates), and Custom Events with dashboard personalization.
Vexo package installation
Install the Vexo package using: npm install vexo-analytics, yarn add vexo-analytics, pnpm add vexo-analytics, or bun install vexo-analytics.
Vexo setup steps for Expo
To set up Vexo: Create a Vexo account at vexo.co, create a new app to receive an API key, install vexo-analytics package, initialize Vexo in your app's entry file with your API key, rebuild your application since vexo-analytics includes native code, then verify integration by checking your app's page on Vexo for the first event.
React Navigation overview
React Navigation is a component-based navigation library widely used across the React Native ecosystem. It lets you compose stack, tab, and drawer navigators entirely in code to implement complex flows, custom transitions, and app-specific UX patterns. The library offers platform-specific look-and-feel with smooth animations and gestures, unified mobile and web routing, automatic deep links, type routes with static configuration, and is highly customizable.
React Native lacks built-in navigation
React Native core includes basic UI components, touch handling, device APIs and networking, but does not include navigation. Navigation is intended to be covered by community libraries.
Navigation library choices for Expo and React Native
For Expo and React Native apps, the main navigation options are React Navigation or Expo Router.
New Expo projects include Expo Router by default
New Expo projects created with npx create-expo-app@latest --template default@sdk-57 include Expo Router by default.
iOS Universal Links require two-way association
To configure iOS Universal Links for your app, you need to set up a two-way association to verify your website and native app. This involves website verification (creating an apple-app-site-association file in /.well-known directory) and native app verification (code signing that references the target website domain).
Create apple-app-site-association (AASA) file location
For Expo Router projects (and other modern React frameworks), create the apple-app-site-association file at public/.well-known/apple-app-site-association. For legacy Expo webpack projects, create it at web/.well-known/apple-app-site-association.
AASA file basic structure
The apple-app-site-association file is a JSON file containing an 'applinks' section with 'apps' array and 'details' array. Each detail object requires: appID (syntax: '<APPLE_TEAM_ID>.<BUNDLE_ID>') and paths array (paths that should support redirecting). The activitycontinuation and webcredentials objects are optional but recommended.
AASA wildcard matching rules
The * wildcard in AASA paths does not match domain or path separators (periods and slashes). For example, /records/* matches /records/1 but not /records/sub/1.
AASA details format with appIDs and components
As of iOS 13, the details format supports appIDs (array) instead of appID (single), and a components array that allows you to specify fragments, exclude specific paths, and add comments. The components array can include properties like '/', '#', '?', 'exclude', and 'comment'.
AASA file must be served over HTTPS
The apple-app-site-association file must be served over an HTTPS connection. Verify that your browser can access the file before proceeding.
Configure associatedDomains in app.json
After deploying the AASA file, add ios.associatedDomains to your app config. Follow Apple's specified format and do not include the protocol (https) in the URL. For example, for https://expo.dev/, use 'applinks:expo.dev' in the associatedDomains array.
AASA configuration example
Example app.json configuration: {"expo": {"ios": {"associatedDomains": ["applinks:expo.dev"]}}}