skip to content

In an Expo project, what does the app config's scheme field do, and why does changing it require a new build rather than an update?

level: juniorimportance: should knowfreq 38%

answer

  1. prebuild writes the native files
  2. CFBundleURLTypes and an intent filter
  3. string or array of schemes
  4. added, never removed: --clean
  5. native change, so a new build

basics

~20 s

Expo's scheme field declares the app's custom URL scheme; prebuild writes it into Info.plist's CFBundleURLTypes and an Android VIEW intent filter. That is native configuration, so it needs a new build — an over-the-air update cannot add it.

solid answer

~40 s

In `app.json` or `app.config.ts`, `"scheme": "resetapp"` (or an array of schemes) declares the custom scheme. During prebuild, Expo's config plugins write it into the native projects: a `CFBundleURLTypes` entry in `Info.plist`, and a `VIEW` intent filter with `DEFAULT` and `BROWSABLE` categories on the `singleTask` main activity in `AndroidManifest.xml`. Because the result lives in the binary, changing the scheme needs a new development or store build; an EAS Update ships only JavaScript and assets. Two details: if no scheme is set, prebuild falls back to `android.package` and `ios.bundleIdentifier` as schemes, and the Android step only adds schemes, so removing one cleanly needs `npx expo prebuild --clean`. Expo Go uses its own `exp://` scheme, so the app's scheme is tested in a development build.

code

typescript · 11 lines
typescript
import type { ExpoConfig } from 'expo/config';

const config: ExpoConfig = {
  name: 'Reset App',
  slug: 'reset-app',
  scheme: 'resetapp',
  ios: { bundleIdentifier: 'com.example.resetapp' },
  android: { package: 'com.example.resetapp' },
};

export default config;

go deeper

for a junior

Know that the scheme field in app.json declares the app's custom URL scheme and that it takes effect in a new build.

for a middle

Explain what prebuild writes on each platform, why an over-the-air update cannot change it, and why Expo Go cannot test it.

for a senior

Handle scheme renames with prebuild --clean, account for the bundle identifier and package fallbacks, and plan builds around native config changes.

for a principal

Treat schemes as part of the native release surface: coordinate changes with store builds and keep old links working across versions.

## The field In an **Expo** project the app config (`app.json`, `app.config.js` or `app.config.ts`) has a top-level **`scheme`** property: ```json { "expo": { "scheme": "resetapp" } } ``` It also accepts an **array** of strings, and platform-specific `ios.scheme` / `android.scheme` values are merged in. Declaring it is how an Expo app claims `resetapp://` so that a link such as `resetapp://reset-password?token=…` opens the app. ## What prebuild does with it Expo generates or updates the native `ios/` and `android/` projects during **prebuild** — explicitly with `npx expo prebuild`, or as a step of EAS Build for a project that does not commit its native folders. Config plugins translate `scheme` into native configuration: | Platform | What is written | Where | |---|---|---| | iOS | `CFBundleURLTypes` with the schemes in `CFBundleURLSchemes`; the iOS plugin also appends `ios.bundleIdentifier` when set | `Info.plist` | | Android | a `VIEW` intent filter with `DEFAULT` and `BROWSABLE` categories and one `<data android:scheme>` per scheme | the `singleTask` activity in `AndroidManifest.xml` | Details worth knowing: - **Fallback when unset.** Expo's linking guide says that without a `scheme`, prebuild uses `android.package` and `ios.bundleIdentifier` as the default schemes. - **Add-only on Android.** The Android scheme step adds schemes that are missing but does not remove old ones, because other plugins may have added schemes too. To drop a scheme cleanly, regenerate the native projects with `npx expo prebuild --clean`. - **A `singleTask` activity is required.** If the Android manifest has no activity with `launchMode="singleTask"`, the plugin warns and cannot add the schemes. ## Why it needs a build, not an update 1. The OS reads URL registrations from the **installed binary** — `Info.plist` and `AndroidManifest.xml`. 2. `scheme` only reaches those files through prebuild, which runs as part of a native build. 3. An **EAS Update** (over-the-air update) delivers a new JavaScript bundle and assets to an existing binary; it cannot change native configuration. So after adding or changing `scheme`, create a new **development build** to test, and a new store build to ship. Expo's linking guide says exactly that: after adding a custom scheme, create a new development build. ## Testing it - **Not in Expo Go.** Expo Go is a prebuilt app with its own `exp://` scheme; your `scheme` is not in its binary. In Expo Go, deep links look like `exp://127.0.0.1:8081/--/reset-password`, where `/--/` separates Expo Go's own URL from the app path. - **In a development build**, test with `npx uri-scheme open resetapp://reset-password --ios` (or `--android`), or by tapping a link. - `Linking.createURL` from `expo-linking` builds a URL with the right scheme for the current environment — `resetapp://…` in a build, `exp://…` in Expo Go — which avoids hard-coding it. ## Common mistakes - Changing `scheme`, running `npx expo run:ios` on existing native folders, and expecting the new scheme: when native folders already exist locally, rerun `npx expo prebuild --clean` first, as Expo's guides say for scheme changes. - Testing the new scheme in Expo Go and concluding the config is broken. - Hard-coding `resetapp://` in JavaScript instead of building URLs with `Linking.createURL`, so development in Expo Go produces links that go nowhere. ## Version note This describes Expo SDK 57, which runs React Native 0.86. The `scheme` field and its prebuild behaviour are long-standing; what changes across SDKs is mostly how routing libraries consume the URL, not how the scheme is registered. ## What a strong answer shows It says `scheme` is native configuration applied by prebuild, names what lands in each platform's files, explains why an over-the-air update cannot change it, and knows the testing consequences: not in Expo Go, rebuild the development build, and `--clean` to remove a scheme.

  • Why does a resetapp:// link not open your project in Expo Go?
    Expo Go is a prebuilt app whose binary registers its own `exp://` scheme, not yours. In Expo Go, links use `exp://<host>/--/path`. To test `resetapp://`, install a development build, whose native projects were generated from your `scheme`.
  • You renamed the scheme and rebuilt, but the old one still opens the Android app. Why?
    The Android scheme step only adds missing schemes to the manifest; it does not remove existing ones, since other plugins may have added them. If the native folders are kept between builds, the old scheme stays. Run `npx expo prebuild --clean` to regenerate them.

saying these in an interview costs you the question

  • Changing the scheme can be shipped to users with an EAS Update.
  • Expo Go opens links with the app's own custom scheme.
  • Removing a scheme from app.json always removes it from AndroidManifest.xml.
  • Without a scheme field, an Expo build has no URL scheme at all.
  • The scheme field is read by JavaScript at runtime to register the scheme.