Prebuild Basics
Ten examples for generating ios/ and android/ from Expo config without manual drift - the foundation of Continuous Native Generation (CNG) on Expo SDK 57 and React Native 0.86.
Search across all documentation pages
Ten examples for generating ios/ and android/ from Expo config without manual drift - the foundation of Continuous Native Generation (CNG) on Expo SDK 57 and React Native 0.86.
Start from a blank Expo app with SDK 57 pinned. Prebuild reads app.config.ts, applies config plugins, and writes native projects:
npx create-expo-app@latest PrebuildBasics --template blank-typescript@sdk-57 --yes
cd PrebuildBasicsConfirm the stack pin before running prebuild:
{
"dependencies": {
"expo": "~57.0.4",
"react": "19.2.3",
"react-native": "0.86.0"
}
}Tooling: These examples target Expo SDK 57 (
expo~57.0.4), React Native 0.86.0, and React 19.2.3. Read Native Module Rules before adding native dependencies.
npx expo prebuild is the bridge between your JavaScript config and Xcode/Gradle projects. It:
app.config.ts (or app.json)ios/ and android/Info.plist, AndroidManifest.xml, Gradle, and Podfilenpx expo prebuildAfter a successful run:
PrebuildBasics/
├── app.config.ts
├── ios/ # Generated - do not hand-edit under CNG
├── android/ # Generated - do not hand-edit under CNG
└── package.jsonios/ and android/ - EAS Build runs the same prebuild step in CIexpo prebuild --clean (the SDK 57 default)Prebuild needs bundle identifiers, display name, and icon paths at minimum:
// app.config.ts
import type { ExpoConfig } from "expo/config";
const config: ExpoConfig = {
name: "PrebuildBasics",
slug: "prebuild-basics",
version: "1.0.0",
orientation: "portrait",
icon: "./assets/icon.png",
scheme: "prebuildbasics",
userInterfaceStyle: "automatic",
ios: {
bundleIdentifier: "com.example.prebuildbasics",
supportsTablet: true,
},
android: {
package: "com.example.prebuildbasics",
adaptiveIcon: {
foregroundImage: "./assets/adaptive-icon.png",
backgroundColor: "#ffffff",
},
},
};
export default config;npx expo prebuild --no-install--no-install skips pod install and Gradle sync - useful for CI diff checksnpx expo config --type public prints the resolved manifest without writing native foldersPRODUCT_BUNDLE_IDENTIFIER and applicationIdStandard CNG projects do not commit generated native directories:
# .gitignore (CNG pattern)
ios/
android/# Verify prebuild reproduces the same tree locally
rm -rf ios android
npx expo prebuild --no-installios/ from SDK 56 blocks RN 0.86 templates on upgrade - delete and regenerateRelated: ../expo-rules/expo-project-rules-checklist/expo-project-rules-checklist.md - CNG gitignore is a Tier 3 non-negotiable
In SDK 57, expo prebuild deletes existing ios/ and android/ before regenerating:
# Default behavior (SDK 57): clean then generate
npx expo prebuild
# Opt out: layer changes onto existing native projects
npx expo prebuild --no-clean| Flag | Behavior |
|---|---|
| (default) | Remove ios/ and android/, then generate fresh projects |
--no-clean | Keep existing folders; apply template updates incrementally |
--platform ios | Generate iOS only |
--platform android | Generate Android only |
--no-clean when iterating on a local native patch you are migrating into a pluginBefore merging plugin changes, diff what prebuild produced:
npx expo prebuild --no-install
git diff ios android # only if folders are tracked (brownfield)# Print resolved config without writing files
npx expo config --type introspect--type introspect shows mod results plugins would apply - faster than full prebuild during plugin developmentexpo-doctor catches invalid config fields that fail silently until prebuildapp.config.ts matters when multiple plugins touch the same fileNative permissions and entitlements belong in plugins, not post-prebuild edits:
// app.config.ts
const config: ExpoConfig = {
// ...name, slug, ios, android
plugins: [
[
"expo-camera",
{
cameraPermission: "Allow $(PRODUCT_NAME) to scan barcodes.",
},
],
],
};npx expo install expo-camera
npx expo prebuild --platform ios --no-installexpo-camera ships a config plugin that adds NSCameraUsageDescription to Info.plistCustom native modules require a development build, not Expo Go. Prebuild generates the native shell:
npx expo install expo-dev-client
npx expo prebuild
npx expo run:ios// package.json scripts
{
"scripts": {
"prebuild": "expo prebuild",
"ios:native": "expo run:ios",
"android:native": "expo run:android"
}
}expo run:ios runs prebuild if ios/ is missing, installs pods, then compilesPrebuild reads platform keys and generates matching native settings:
// app.config.ts excerpt
ios: {
bundleIdentifier: "com.example.prebuildbasics",
infoPlist: {
UIBackgroundModes: ["fetch"],
},
associatedDomains: ["applinks:shop.example.com"],
},
android: {
package: "com.example.prebuildbasics",
intentFilters: [
{
action: "VIEW",
autoVerify: true,
data: [{ scheme: "https", host: "shop.example.com", pathPrefix: "/orders" }],
category: ["BROWSABLE", "DEFAULT"],
},
],
},npx expo prebuild --no-installassociatedDomains and intent filtersexpo-router, expo-notifications) over raw infoPlist when availableSDK 57 targets RN 0.86 with the New Architecture enabled in prebuild templates:
// app.config.ts
const config: ExpoConfig = {
// ...
newArchEnabled: true,
};npx expo-doctor
npx expo prebuild --no-installnewArchEnabled: false is a temporary escape hatch - document an ADR if you disable itnewArchEnabled: rm -rf ios android && npx expo prebuildSDK bumps delete stale native folders under CNG:
npx expo install expo@^57.0.0
npx expo install --fix
npx expo-doctor
rm -rf ios android
npx expo prebuild --no-installreact-native independently - expo install --fix pins RN 0.86.0 for SDK 57ios//android/ are absent from the uploadIn a pnpm/Turborepo workspace, run prebuild from the app package directory:
cd apps/mobile
npx expo prebuild --no-install// apps/mobile/app.config.ts
const config: ExpoConfig = {
name: "Mobile",
slug: "mobile",
// ...
};ios/ - only deployable apps run prebuildDrive staging and production bundle IDs from env vars - one config file, multiple binaries:
// app.config.ts
const IS_DEV = process.env.APP_VARIANT === "development";
const config: ExpoConfig = {
name: IS_DEV ? "PrebuildBasics (Dev)" : "PrebuildBasics",
slug: "prebuild-basics",
ios: {
bundleIdentifier: IS_DEV
? "com.example.prebuildbasics.dev"
: "com.example.prebuildbasics",
},
android: {
package: IS_DEV
? "com.example.prebuildbasics.dev"
: "com.example.prebuildbasics",
},
};
export default config;APP_VARIANT=development npx expo prebuild --no-install
APP_VARIANT=production npx expo prebuild --no-installenv.APP_VARIANT per build - prebuild runs with the correct bundle ID in CIprojectId if binaries ship separatelyapp.config.ts - use EAS Secrets for signing keysAdd a CI gate that proves config produces reproducible native output:
#!/usr/bin/env bash
# scripts/verify-prebuild.sh
set -euo pipefail
npx expo-doctor
rm -rf ios android
npx expo prebuild --no-install
# Optional: fail if unexpected native diff when folders are tracked
if git diff --quiet ios android 2>/dev/null; then
echo "Prebuild output matches committed native projects."
else
echo "Native diff detected - migrate changes to config plugins."
exit 1
fiios/ and recreate them.expo run:ios and expo run:android need platform toolchains to compile.--no-clean, local iteration checklistexpo run:ios/android and committing native dirsStack versions: This page was written for React 19.2.3, React Native 0.86.0, and Expo SDK 57 (
expo~57.0.4).
Reviewed by Chris St. John·Last updated Jul 19, 2026