Crash on launch

Works in Expo Go, crashes on launch in TestFlight: how to find and fix it

Last verified by Designated Dev

Short answer

The most common cause is environment variables that never reached the EAS build. EXPO_PUBLIC_ variables are inlined when the app is bundled, and a .env file that is gitignored is not uploaded to EAS Build, so values like your API URL are undefined in the binary and the app throws before its first screen. Other causes are native code Expo Go includes but your build doesn't, and errors that only appear in production mode.

We will reject incomplete app bundles and binaries that crash or exhibit obvious technical problems.

App Review Guideline 2.1(a), App Completeness

At a glance

What you seeWhy it happensThe fix
Closes instantly on open, fine in Expo GoEXPO_PUBLIC_ variables undefined: the .env was gitignored, so EAS Build never saw itStore them as EAS environment variables and set environment in the eas.json build profile
Crashes when a feature using a native library opensExpo Go ships that native code; your build lacks the library or its config pluginTest on a development build; add the config plugin and rebuild
Works in development, fails only in releaseCode that depends on development mode, or an error hidden by the dev serverRun npx expo start --no-dev --minify and fix the error it shows
Crashes when the camera, photos or location is first usedMissing usage description in Info.plistAdd the purpose string through app config or the library's config plugin

Why the preview hides this

Expo Go and a builder’s preview run your JavaScript inside an app that already exists, with its own native code and your local .env values available. A TestFlight build is a new binary, produced on EAS’s servers from what you uploaded. Anything that only existed on your machine, or only inside Expo Go, is missing.

It matters at review time too. In Apple’s 2025 App Store Transparency Report, Performance, the guideline category that covers crashes, accounted for 1,354,418 of the 2,093,244 rejected submissions.

1. Get the real error first

Apple’s crash reports usually show a generic native abort, not the JavaScript error. Expo’s docs say it plainly: Apple’s crash reporting “does not include the exception message.” So reproduce it where you can read the log:

# A release build on your own simulator or device
npx expo run:ios --configuration Release

# JavaScript only: run the bundle in production mode
npx expo start --no-dev --minify

With the device connected, open Console.app, select the device and start streaming. The JavaScript error appears there when the app dies. TestFlight crash reports also appear in Xcode’s Crashes organizer and in App Store Connect under TestFlight feedback.

2. The most common cause: environment variables that never reached EAS

Expo inlines EXPO_PUBLIC_ variables into the bundle when it’s built. EAS Build decides what to upload from your .gitignore (or .easignore), so a .env file that is gitignored, as it usually is, never reaches the build server. In the binary, process.env.EXPO_PUBLIC_API_URL is simply undefined.

If a client such as Supabase or Firebase is created at import time with that value, it throws before the first screen renders. That is the classic instant crash.

The fix is to store the values as EAS environment variables and tell each build profile which environment to use:

eas env:set production --name EXPO_PUBLIC_API_URL --value https://api.example.com --visibility plaintext
{
  "build": {
    "preview": { "distribution": "internal", "environment": "preview" },
    "production": { "environment": "production" }
  }
}

Two rules from Expo’s docs are worth keeping in mind:

  • Read variables as process.env.EXPO_PUBLIC_NAME. Bracket access and destructuring are not inlined.
  • Anything prefixed EXPO_PUBLIC_ ends up readable inside the app. Never put a secret key there. An OpenAI key, for example, belongs on a server.

Older tutorials use eas secret:create. The current commands are eas env:set, eas env:list and eas env:pull. Running eas env:pull production writes a matching .env locally, so your local release build behaves like the real one.

3. Native code that Expo Go had and your build doesn’t

Expo Go ships a fixed set of native libraries and can’t run config plugins. A library can therefore “work” in Expo Go without ever having been set up for a real binary. Build a development build, test the feature there, and add the library’s config plugin if it needs one. npx expo-doctor@latest flags many of these mismatches.

From SDK 55, the New Architecture is always on and can’t be switched off. An older library that isn’t compatible can crash a release build even when the preview looked fine.

4. Crashes on first use of the camera, photos or location

Apple’s documentation says that without the purpose string, attempts to access the resource fail “and might cause your app to crash.” Set the usage descriptions in your app config or through the library’s config plugin, and write each one as the real reason your app needs access.

How we handle it

We rebuild the release binary locally, attach the device log, and fix the cause rather than the symptom: variables moved into EAS environments, secret keys moved behind a server, native libraries configured. Then we test the TestFlight build on a real device before it goes back to review.

Questions

Why does my app work in Expo Go but crash in TestFlight?

Most often because EXPO_PUBLIC_ variables from a gitignored .env file were never uploaded to EAS Build, so they are undefined in the binary. The next most common cause is native code that Expo Go bundles but your build does not include or configure.

Are .env files uploaded to EAS Build?

Only if they are not excluded by .gitignore, or by .easignore if you have one. For cloud builds, store values as EAS environment variables and select them with the environment field in your eas.json build profile.

How do I see why my TestFlight build crashed?

Crash reports appear in Xcode's Crashes organizer and in App Store Connect under TestFlight feedback. They usually lack the JavaScript error message, so reproduce with a local release build and read the device log in Console.app.

What replaced eas secret:create?

The eas env commands. Use eas env:set to create or update a variable with a visibility of plaintext, sensitive or secret, and eas env:list to check what a build will receive.

Will Apple reject an app that crashes on launch?

Yes. Guideline 2.1(a) says Apple will reject binaries that crash or show obvious technical problems, and asks you to test on a device for bugs and stability before submitting.

Sources

  1. Expo: Environment variables in Expo
  2. Expo: EAS environment variables
  3. Expo: Uploading files to EAS Build (.easignore)
  4. Expo: Troubleshooting build errors and crashes
  5. Expo: Debugging runtime issues
  6. Expo: Error recovery in EAS Update
  7. Expo: EAS CLI reference
  8. Apple: Acquiring crash reports and diagnostic logs
  9. Apple: App Review Guidelines, 2.1
  10. Apple: 2025 App Store Transparency Report

Stuck on this in your own app?

We fix it, test it on real devices, and resubmit.

30 minutes with the engineer who'd do the work. Bring the repo link or the rejection email. Fixed price once scoped, in writing.