EAS Build

Prepare development, preview, production, and OTA builds with EAS.

EAS is the native distribution path for this Expo template. Use it for development clients, internal preview builds, store builds, and OTA updates. pnpm web is useful during development, but iOS and Android releases go through EAS Build and EAS Submit.

Project wiring

app.config.ts includes the EAS project ID:

const EAS_PROJECT_ID = "3e167ef9-3e6c-47f2-9d14-01f738667145";

The same file configures:

  • extra.eas.projectId
  • updates.url = https://u.expo.dev/<project-id>
  • runtimeVersion: { policy: "appVersion" }
  • variant-specific display name, bundle identifier, package name, and scheme

When you fork the template, replace the project ID with your own EAS project:

eas login
eas init

Then commit the updated app.config.ts value.

Build profiles

eas.json ships three profiles:

ProfileDistributionChannelNative detailsEnv
developmentInternaldevelopmentdevelopmentClient: trueAPP_VARIANT=development, SENTRY_ENVIRONMENT=development
previewInternalpreviewAndroid APK, iOS device build, no iOS simulatorAPP_VARIANT=preview, SENTRY_ENVIRONMENT=preview
productionStoreproductionautoIncrement: trueAPP_VARIANT=production, SENTRY_ENVIRONMENT=production

The CLI block uses:

{
  "version": ">= 16.0.0",
  "appVersionSource": "remote"
}

Remote app version source means EAS owns build numbers. Use eas build:version:get and eas build:version:set when you need to inspect or manually adjust versions.

App variants

The template can install development, preview, and production builds side by side:

VariantNameBundle ID / packageScheme
DevelopmentSoar Starter (Dev)expo.soarstarter.com.devsoar-starter-expo-dev
PreviewSoar Starter (Preview)expo.soarstarter.com.previewsoar-starter-expo-preview
ProductionSoar Starterexpo.soarstarter.comsoar-starter-expo

Run a local Metro session with the same variant:

pnpm dev
pnpm dev:preview
pnpm dev:prod

Use matching .env.local, .env.preview, or your shell/EAS environment values so the bundle gets the right Supabase project, RevenueCat keys, link domain, and store URLs.

Environment and secrets

EXPO_PUBLIC_* values are public. They are inlined into the JavaScript bundle and can be inspected by anyone with the app binary.

Put publishable client values in Expo/EAS env:

EXPO_PUBLIC_SUPABASE_URL=
EXPO_PUBLIC_SUPABASE_ANON_KEY=
EXPO_PUBLIC_REVENUECAT_IOS_KEY=
EXPO_PUBLIC_REVENUECAT_ANDROID_KEY=
EXPO_PUBLIC_POSTHOG_API_KEY=
EXPO_PUBLIC_SENTRY_DSN=

Keep server secrets out of the client bundle:

SecretWhere it belongs
REVENUECAT_WEBHOOK_AUTHSupabase Edge Function secret
APNs keys for optional send-test-pushSupabase Edge Function secrets
SENTRY_AUTH_TOKENEAS secret or CI secret for sourcemap upload
Supabase service role keyNever in Expo env; Supabase injects it into Edge Functions

SENTRY_ENVIRONMENT is set per EAS build profile. Add SENTRY_AUTH_TOKEN when you upload sourcemaps.

Build commands

Install a development client:

eas build --profile development --platform ios
eas build --profile development --platform android

Create internal preview builds:

eas build --profile preview --platform ios
eas build --profile preview --platform android

Create store builds:

eas build --profile production --platform ios
eas build --profile production --platform android

Cloud builds are the normal path. Local builds are useful for debugging native configuration, but they still need local Xcode or Android tooling:

eas build --profile development --platform ios --local
eas build --profile development --platform android --local

After installing a development build, start Metro:

pnpm dev

Then open the installed dev client. Expo Go can render many screens, but native modules such as purchases and Google Sign-In require a development or store build.

OTA updates

The template includes expo-updates:

runtimeVersion: { policy: "appVersion" },
updates: { url: "https://u.expo.dev/<project-id>" }

Channels map to the EAS profiles:

ChannelBuild profileUse
developmentdevelopmentDeveloper testing on dev clients
previewpreviewInternal QA updates
productionproductionStore build updates

Publish an OTA update to the matching channel:

eas update --channel preview --message "Fix onboarding copy"
eas update --channel production --message "Update paywall text"

OTA can ship JavaScript, styling, copy, and assets bundled with the update. It cannot ship new native modules, config plugin changes, new permissions, bundle ID changes, or store metadata changes. Those require a new EAS build.

Because the runtime policy is appVersion, an OTA update only reaches builds with the same app version. If an update does not arrive, check both channel and runtime version first.

Submit teaser

Once a production build is ready, submit it to the stores:

eas submit --profile production --platform ios
eas submit --profile production --platform android

The template's submit.production object is empty, so the first submit prompts for store credentials and app selection. Store-specific preparation is covered in Store Release.

On this page