Project Setup Basics
10 examples to get you started with Project Setup - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with Project Setup - 7 basic and 3 intermediate.
Scaffold a production-shaped Expo app with an explicit SDK 57 pin. The default@sdk-57 template ships Expo Router, TypeScript, and the recommended CLI scripts.
npx create-expo-app@latest MyApp --template default@sdk-57
cd MyApp
npm installConfirm the SDK pin before adding features:
{
"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.
A fresh default@sdk-57 project is a small, predictable tree - learn these paths before adding features or monorepo packages.
MyApp/
├── app/ # Expo Router screens and layouts
│ ├── _layout.tsx # Root stack / providers
│ ├── index.tsx # "/" route
│ └── (tabs)/ # Tab group (template starter)
├── assets/ # Icons, splash, images referenced in config
├── app.config.ts # Dynamic manifest (name, bundle ID, plugins)
├── package.json # SDK-pinned deps and npm scripts
├── tsconfig.json # Extends expo/tsconfig.base
├── babel.config.js # Expo preset for Metro
├── metro.config.js # Metro bundler entry (often default export)
└── node_modules/app/ replaces a single App.tsx - file names map to routes (app/settings.tsx → /settings)app/_layout.tsx is the root layout: wrap providers, fonts, and navigation chrome hereassets/ files are referenced by URI in app.config.ts (icon, splash) and imported in componentsios/ or android/ in pure managed mode - they appear after npx expo prebuild or an EAS buildRelated: create-expo-app Templates - blank, tabs, and bare-minimum trade-offs | Folder Structure for Features - scaling past the starter tree
The expo package version is the contract every other Expo module must satisfy.
{
"name": "my-app",
"version": "1.0.0",
"main": "expo-router/entry",
"private": true,
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"expo": "~57.0.4",
"react": "19.2.3",
"react-native": "0.86.0"
}
}expo ~57.0.4 locks the project to SDK 57 - mixing SDK 56 native modules with SDK 57 expo causes build failuresmain: "expo-router/entry" wires Metro to Expo Router's bootstrap instead of expo/AppEntry.jsnpx expo install uses them as the compatibility baselinenpm install then npx expo-doctor before writing feature codeRelated: create-expo-app Templates - template-specific dependency sets | ../expo-platform/create-expo-app-quickstart.md - scaffold flags and verification checklist
Default scripts delegate to the Expo CLI so every teammate uses the same dev-server flags.
{
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"lint": "expo lint",
"typecheck": "tsc --noEmit"
}
}# Common start variants - pass flags after --
npm run start -- --clear # bust Metro cache after dependency changes
npm run start -- --tunnel # share QR across networks (slower)
npm run start -- --dev-client # open in a custom development build
npm run ios # shorthand for simulatorexpo start owns Metro, the dev menu, and QR codes - avoid calling react-native start directly in Expo projectsandroid, ios) are shortcuts; they still launch Metro first, then open the targettypecheck early so CI can gate merges without a full native compilenpx expo install <pkg> when adding native modules - it reads the pinned expo version and picks compatible semver rangesRelated: Project Setup Best Practices - scripts every team should standardize on
Extend Expo's base TypeScript config, then tighten checks without breaking Metro's path resolution.
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"paths": {
"@/*": ["./*"]
}
},
"include": [
"**/*.ts",
"**/*.tsx",
".expo/types/**/*.ts",
"expo-env.d.ts"
]
}extends: "expo/tsconfig.base" ships the correct jsx, moduleResolution, and Expo Router typesstrict: true catches nullability bugs early - mobile crashes are harder to debug than web console errorspaths with @/* matches common import aliases; Metro reads the same mapping via tsconfigPaths in newer Expo templates.expo/types/**/*.ts so typed routes generated by Expo Router are part of tsc --noEmitRelated: ../typescript-rn/expo-router-typed-routes/expo-router-typed-routes.md - typed routes generated into
.expo/types
app.config.ts is the single source of truth for app identity, icons, permissions, and config plugins.
import { ExpoConfig, ConfigContext } from "expo/config";
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
name: "My App",
slug: "my-app",
version: "1.0.0",
orientation: "portrait",
icon: "./assets/icon.png",
scheme: "myapp",
userInterfaceStyle: "automatic",
newArchEnabled: true,
ios: {
supportsTablet: true,
bundleIdentifier: "com.example.myapp",
},
android: {
adaptiveIcon: {
foregroundImage: "./assets/adaptive-icon.png",
backgroundColor: "#ffffff",
},
package: "com.example.myapp",
},
plugins: ["expo-router"],
experiments: {
typedRoutes: true,
},
});slug drives Expo dashboard URLs and update channels - change it deliberately alongside name and bundle IDsscheme registers deep-link prefixes consumed by Expo Router and expo-linkingplugins run at prebuild time - they are ignored by Expo Go when the plugin is not bundled in the clientapp.config.ts over app.json when you need environment branching without committing secretsRelated: ../expo-platform/expo-platform-basics/expo-platform-basics.md - managed workflow and config overview
Run expo-doctor after every scaffold, clone, or dependency bump - it compares your graph to the SDK 57 compatibility database.
npx expo-doctor# Typical fix path when doctor reports version skew
npx expo install --fix
npx expo-doctorExample output to act on (not ignore):
✖ expo-camera@15.0.0 - expected ~17.0.0 for SDK 57
✖ react-native-reanimated@3.16.0 - expected ~4.0.0 for SDK 57
› Run: npx expo install --fixexpo install --fix rewrites package.json ranges to SDK-compatible versions without guessing semver manuallymainRelated: ../expo-platform/upgrading-expo-sdk-versions/upgrading-expo-sdk-versions.md - intentional SDK bumps
Metro and Babel configs are thin in a standard Expo app - keep them minimal unless monorepo resolution requires changes.
// babel.config.js
module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
};
};// metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);
module.exports = config;babel-preset-expo enables Reanimated, Expo Router, and environment transforms expected by SDK 57getDefaultConfig from expo/metro-config sets resolver, transformer, and asset extensions for the pinned SDKexpo asset hashingwatchFolders or custom resolvers - see the shared-packages guide before editingRelated: Shared Packages & Metro Resolution -
watchFoldersand symlink pitfalls
A short verification script catches SDK drift, TypeScript errors, and config mistakes before anyone writes feature code.
#!/usr/bin/env bash
# scripts/verify-first-run.sh
set -euo pipefail
echo "→ Checking SDK pin…"
node -e "
const p = require('./package.json').dependencies;
const expected = { expo: '~57.0.4', react: '19.2.3', 'react-native': '0.86.0' };
for (const [k, v] of Object.entries(expected)) {
if (p[k] !== v) throw new Error(\`Expected \${k}=\${v}, got \${p[k]}\`);
}
console.log(' SDK pin OK');
"
echo "→ Running expo-doctor…"
npx expo-doctor
echo "→ Typechecking…"
npx tsc --noEmit
echo "→ Resolving public config…"
npx expo config --type public | head -n 20
echo "✅ First-run checks passed - run: npm run start"chmod +x scripts/verify-first-run.sh
./scripts/verify-first-run.shnpm install on every fresh clone - lockfiles can be correct while a teammate's global CLI is stale@sdk-57expo config --type public confirms plugins and bundle identifiers resolve - catch typos in app.config.ts earlyRelated: ../expo-platform/create-expo-app-quickstart/create-expo-app-quickstart.md - reproducible team scaffold script
Branch app identity on APP_VARIANT so dev, staging, and production builds coexist without separate repos.
import { ExpoConfig, ConfigContext } from "expo/config";
const APP_VARIANT = process.env.APP_VARIANT ?? "development";
const bundleSuffix = APP_VARIANT === "production" ? "" : `.${APP_VARIANT}`;
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
name: APP_VARIANT === "production" ? "My App" : `My App (${APP_VARIANT})`,
slug: "my-app",
ios: {
bundleIdentifier: `com.example.myapp${bundleSuffix}`,
},
android: {
package: `com.example.myapp${bundleSuffix}`,
},
extra: {
appVariant: APP_VARIANT,
},
});# Launch with a variant - name and bundle ID change per command
APP_VARIANT=development npx expo start
APP_VARIANT=staging npx expo run:ios
APP_VARIANT=production npx eas build --profile production --platform allAPP_VARIANT is a team convention - Expo does not define it; consistency matters more than the exact nameextra so runtime code can gate logging, API base URLs, or feature flagsenv.APP_VARIANT per channel - avoid hard-coding URLs in sourceRelated: Multiple Apps in One Repo - white-label and flavor-specific apps | ../expo-platform/environments-and-eas-environment-variables/environments-and-eas-environment-variables.md - per-environment secrets
Commit the lockfile and a mobile-aware .gitignore on day one - reproducible installs matter for EAS and teammates.
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
# Native (managed workflow - generated at build)
ios/
android/
# Secrets
.env
.env.*
!.env.examplegit init
git add .
git commit -m "chore: scaffold MyApp (default@sdk-57)"{
"engines": {
"node": ">=20.0.0"
}
}package-lock.json, yarn.lock, or pnpm-lock.yaml) - EAS uses it to reproduce installs.expo/ - local dev state; it should not merge across machinesios/ and android/ in managed workflow until you intentionally adopt Continuous Native Generation (CNG) or bare workflowengines.node so CI and laptops run the same Node major - SDK 57 tooling expects Node 20+Related: Monorepo with Turborepo - lockfile strategy when apps share packages | Project Setup Best Practices - conventions that scale past the first sprint
Stack 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 16, 2026