skip to content

Expo (React Native)

1 roadmap107 questionsupdated

Expo is the toolchain most React Native apps start from: prebuild, an SDK of native packages, file-based routing, EAS builds and OTA updates. Interviewers ask what it adds over bare React Native.

on this pageshow

guide

overview

~1 min

Expo is a framework and a set of hosted services layered on React Native: a generator for the native projects, an SDK of native packages released together, a file-based router, and services that build, submit and update the app. Interviewers ask about it because most new React Native projects start there, and because "what does Expo add over bare React Native?" separates candidates who have shipped an app from those who have only run one in a simulator. A strong answer names which layer a change lives in — JavaScript, native configuration or the binary — and what it takes to get that change to users. The hub splits along that lifecycle. [Workflows & Prebuild](/topics/mob-expo-workflow) covers how native projects are generated and when a project leaves Expo Go for a development build. [SDK Packages](/topics/mob-expo-sdk) is the native API surface: upgrades, permissions, device capabilities and your own modules. [File-System Router](/topics/mob-expo-router) turns a directory of files into navigators, URLs and deep links. [Native Config Plugins](/topics/mob-expo-config-plugins) are how a project with no committed native folders still changes them. [EAS Build & Submit](/topics/mob-expo-eas) turns profiles into signed binaries and sends them to the stores, and [OTA Updates](/topics/mob-expo-ota) ships new JavaScript to apps that are already installed. Junior rounds stay close to the workflow: Expo Go versus a development build, what prebuild generates, how files become routes. Senior rounds turn into release engineering — runtime versions, credentials, staged rollouts, and what happens on devices when a published update crashes at launch. Learn prebuild and development builds first, because every later section assumes you can tell which changes need a new binary. Then the router, then config plugins, and finally build and update, which make sense only as a pair.

primer

### Native projects are build output With **Continuous Native Generation** the `ios` and `android` folders are generated from the app config, the SDK's template and the config plugins instead of being edited by hand and committed. This is what replaced ejecting. A project can still commit its native folders, but then upgrades and native edits go back to being manual work — interviewers want the trade named, not a claim that one mode is right for every team. ### Three layers, three ways to ship Every change lands in one of three places, and the place decides the delivery path: - **JavaScript and assets** can reach installed builds over the air. - **Native configuration and native code** — permissions, entitlements, native libraries, plugin options — need a new binary to take effect, which for most users means a store release. - **The SDK version** moves React Native and every Expo package together, so it is a native change by definition. Many questions in this hub are this classification in disguise. ### The SDK is one tested set Each Expo SDK release is pinned to one React Native release, and its packages are verified together. Installing libraries through Expo's installer keeps a project inside that tested set; an upgrade moves the whole set at once, which is why teams take SDKs one release at a time rather than leaping several. ### Expo Go is a sandbox, not your app Expo Go bundles one frozen selection of native modules for one SDK. Once a project needs a native library that set lacks, its own push credentials or a custom config plugin, it needs a **development build**: its own debug binary with a dev client inside. Knowing where that line sits is the entry-level question of the hub. ### Routes are files, URLs come free Expo Router derives navigators and URLs from the file tree, so every screen has a path, deep links resolve without a hand-written linking table, and the same tree can render on the web. React Navigation's navigators still run underneath, so questions about stack and tab behaviour keep much of their React Navigation answer. ### The binary and the update must agree EAS Update delivers JavaScript through the **channel** compiled into each build, and only when the build's **runtime version** matches the update's. The runtime version is the contract between the native layer and the bundle: set its policy too loosely and JavaScript reaches a binary missing the native code it calls; too strictly and users stay on old code until the next store release.

Continuous Native Generation
Treating the ios and android projects as generated output, recreated from app config, templates and plugins rather than maintained by hand and committed.
Prebuild
The Expo CLI step that generates the native iOS and Android projects from the app config and plugins; it runs locally or inside a cloud build.
App config
The app.json or app.config file holding the app's name, identifiers, icons, permissions, plugins and update settings; the input that prebuild and the EAS services read.
Expo Go
A store app that loads Expo projects over the network. It contains a fixed native module set for a single SDK version and cannot add more.
Development build
A debug binary of your own app that includes expo-dev-client, your native libraries and your native config, used in place of Expo Go.
Expo SDK
The versioned set of Expo native packages and tooling; each release is built and tested against one React Native version.
Config plugin
A function listed in the app config that adjusts the Expo config and registers edits to native files, applied when prebuild runs.
Mod
The part of a config plugin that edits one native file, such as Info.plist, the Android manifest or a Gradle file, during prebuild.
Expo Modules API
Expo's Swift and Kotlin interface for writing native modules and views callable from JavaScript; an alternative to spec-based Turbo Modules.
Expo Router
Expo's file-based router: files under the app directory become screens, layout files become navigators, and every screen gets a URL.
EAS Build
Expo's hosted build service that turns a project and a build profile into signed iOS and Android binaries; the same process can run locally.
Build profile
A named entry in eas.json describing one kind of binary, such as development, preview or production, with its distribution, environment and update channel.
EAS Update
Expo's over-the-air service that publishes JavaScript and asset bundles to installed builds, organised into branches and delivered through channels.
Runtime version
A label for the native layer a build contains. An update is served only to builds whose runtime version and platform match it.
Channel
A name compiled into a build that points at one branch, deciding which stream of updates that build receives.
Branch
An ordered list of published updates. Channels point at branches, so promoting a tested release can be a matter of repointing a channel.

Follow one app from repository to device. The source of truth is the JavaScript code plus the app config. Prebuild reads the config, applies the config plugins in order, links the installed native packages and writes the two native projects. Locally, compiling those projects gives you a development build; in the cloud, EAS Build does the same from a build profile, signs the result with credentials it stores or you supply, and EAS Submit hands the binary to the stores. From then on EAS Update keeps the JavaScript moving: a publish lands on a branch, each build's baked-in channel selects a branch, and the runtime version filters out updates the binary cannot run. The sections map onto that path by *when* they act: - **Config plugins** exist only at prebuild time. They never run on a device, so their output is fixed into a binary. - **SDK packages** have a JavaScript half and a native half; adding a package or a permission is a native change, calling an existing one is not. - **The router** is JavaScript. New screens and routes can travel over the air as long as they need no new native code. Build profiles are where the build and update halves meet: ```json { "build": { "development": { "developmentClient": true, "distribution": "internal" }, "preview": { "distribution": "internal", "channel": "preview" }, "production": { "channel": "production", "autoIncrement": true } } } ``` The `channel` in each profile is compiled into the binary it produces, which is how a preview build and a production build of the same commit end up on different update streams. What the profile does not decide is compatibility: that comes from the runtime version policy in the app config, and a hand-managed runtime version left unchanged after a native change is the classic way an update reaches a binary it breaks.

  1. Continuous Native Generation →

    What prebuild generates and why native folders are treated as output; every later section assumes this model.

  2. Development Build Clients →

    When Expo Go stops being enough and how a development build replaces it for day-to-day work.

  3. Directory Layout Conventions →

    How the app directory and layout files become screens and navigators, the base for every routing question.

  4. Applying & Ordering →

    How native changes are expressed as config, and why they only take effect after a fresh prebuild and build.

  5. Profile Definitions →

    How one codebase yields development, preview and production binaries, and where each build's update channel is set.

  6. Runtime Version Policies →

    The compatibility contract that decides which builds may receive an update; read it before channels and rollouts.

  • Promising an over-the-air hotfix for a change that touches native code: a new permission, native library or plugin option has to ship in a fresh binary.

  • Describing Expo as Expo Go plus an eject button; development builds and config plugins cover custom native code without leaving Expo's tooling.

  • Changing a config plugin option, then rebuilding from committed or stale native folders so prebuild never re-ran — see this question.

  • Adding a native library with a plain npm install and getting a release built for a different React Native version than the SDK expects.

  • Treating EXPO_PUBLIC_ variables as secret: they are inlined into the shipped bundle, readable by anyone who unpacks the app.

  • Reading params with the global hook in every stacked screen, so background screens re-render on each navigation; the local hook is the usual default.

  • Publishing an update without checking which channel and runtime version it targets, then wondering why no installed build downloads it.

  • Jumping several SDK versions in one upgrade, which merges a series of small, attributable breakages into one large one.

This guide assumes Expo SDK 57, which targets React Native 0.86, and the Expo Router release that ships with it. Expo is easy to answer about in the wrong era, because several long-standing workflows were retired and interviewers who maintain older apps still ask about them: - **Ejecting** gave way to prebuild and Continuous Native Generation. "Managed" and "bare" now describe whether native folders are generated or committed, not whether custom native code is allowed. - **The global `expo-cli`** was deprecated in favour of the CLI bundled with every project's `expo` dependency, so the CLI version follows the project's SDK. - **Classic updates** were replaced by EAS Update, with channels, branches and runtime versions as the compatibility model. - **SDK 53** removed Android remote push from Expo Go, one of the clearer signals that real apps are expected to run in development builds. Because each SDK pins one React Native release and Expo Go runs one SDK, say which SDK you mean whenever an answer depends on a default, a package API or what Expo Go can do.

The comparison interviewers expect first is with a React Native app built without Expo's tooling, where the native projects are committed and edited by hand and every upgrade is a manual diff. Expo sits on top of React Native rather than replacing it, and an Expo project can still reach native code through config plugins and its own modules. Inside the React Native world, each Expo piece has a neighbour. Expo Router runs on React Navigation and competes with configuring its navigators by hand; the trade is URLs and deep links for free against a structure dictated by the file tree. The Expo Modules API sits beside Turbo Modules: a declarative Swift and Kotlin module definition on one side, a typed spec with generated bindings on the other. EAS Build and EAS Submit compete with running native builds and store uploads yourself on a general CI service with tools such as fastlane, which gives more control and more upkeep. EAS Update competes with self-hosted over-the-air servers. Flutter and Kotlin Multiplatform compete with React Native as a whole, not with Expo; placing them one level up shows you know what Expo is.

explore

report an issue with this guide →

questions

107 · 6 sections

In an Expo project, what is the difference between a static app.json and a dynamic app.config.ts, and when do you need the dynamic one?

level: juniorimportance: must knowfreq 52%
basics
~20 s

app.json is static JSON that CLI tools can also edit; app.config.ts is code Expo CLI evaluates in Node, so it can read environment variables and compute values. When both exist, app.json is read first and handed to the dynamic config.

open as a page

In an Expo project, what does npx expo prebuild do, and why are the ios and android folders usually left out of git?

level: juniorimportance: must knowfreq 58%
basics
~20 s

npx expo prebuild generates the ios and android native projects from the SDK's template, the app config and its config plugins, plus autolinked modules. Because they can be regenerated at any time, they are treated as build output and gitignored.

open as a page

In Expo, what is the difference between Expo Go and a development build, and when does a project outgrow Expo Go?

level: juniorimportance: must knowfreq 68%
basics
~20 s

Expo Go is a prebuilt app with a fixed set of native libraries for one SDK. A development build is your own debug binary with expo-dev-client, holding your native code and config. Outgrow Expo Go when you need native code it lacks.

open as a page

How do you start a new Expo project with create-expo-app, and how do its default, blank, tabs and bare-minimum templates differ?

level: juniorimportance: must knowfreq 45%
basics
~10 s

Run npx create-expo-app with an optional --template: default adds Expo Router and TypeScript for multi-screen apps, blank is minimal, tabs sets up file-based tab navigation, and bare-minimum also generates the android and ios folders.

open as a page

In an Expo project, how do EXPO_PUBLIC_ variables from .env files get into the app, and what limits does build-time inlining impose?

level: middleimportance: must knowfreq 58%
basics
~20 s

Expo CLI loads .env files, and Metro replaces each static process.env.EXPO_PUBLIC_NAME reference in your code with its literal value when it builds a release bundle. In a release bundle the value is frozen as plain text until the next build or update.

open as a page

In an Expo app, how do you take a photo with expo-camera's CameraView, and what must the code wait for before capturing?

level: juniorimportance: must knowfreq 50%
basics
~20 s

Render CameraView once camera permission is granted, keep a ref, wait for onCameraReady, then call ref.current.takePictureAsync(options). It resolves with a uri to a temporary cache file, so copy it somewhere permanent before relying on it.

open as a page

In an Expo project, why should you add libraries with npx expo install rather than npm install, and which version does it choose?

level: juniorimportance: must knowfreq 60%
basics
~20 s

npx expo install looks the package up in the installed SDK's list of known-compatible versions and installs that version with your package manager. npm install takes the latest release, which may target a different React Native version.

open as a page

When would you write a native module with the Expo Modules API rather than a spec-based Turbo Module, and how do the two differ?

level: middleimportance: must knowfreq 45%
basics
~20 s

The Expo Modules API declares a module in a Swift and Kotlin DSL and validates arguments at runtime; a Turbo Module starts from a TypeScript spec that Codegen turns into native interfaces. Both run over JSI; C++ work favours Turbo Modules.

open as a page

In an Expo app, how do you use a PermissionResponse's status and canAskAgain to handle a user who permanently denied location access?

level: middleimportance: must knowfreq 55%
basics
~20 s

When status is 'denied' and canAskAgain is false, the system will not show the prompt again, so explain why location helps, offer Linking.openSettings(), and re-read the permission with the get function when the user comes back.

open as a page

What does it mean that each Expo SDK targets one React Native version, and how does that constrain which React Native version your Expo app runs?

level: middleimportance: must knowfreq 50%
basics
~20 s

An Expo SDK release is built and tested against a single React Native version, and its packages expect that version. SDK 57 targets React Native 0.86.3, so moving to a newer React Native means moving to the SDK that targets it.

open as a page

With Expo Router, how does the src/app directory turn files into routes, and what does a _layout.tsx file add to that tree?

level: juniorimportance: must knowfreq 62%
basics
~20 s

Each file under src/app with a default export becomes a screen whose URL mirrors its path, and each _layout.tsx wraps its directory's routes in a navigator such as Stack, Tabs or Slot, so the folder tree becomes the navigator tree.

open as a page

In Expo Router, how does a src/app/product/[id].tsx file capture a URL segment, and how does the screen read it?

level: juniorimportance: must knowfreq 60%
basics
~10 s

Square brackets make a dynamic segment: src/app/product/[id].tsx matches /product/42 and any other single segment, and the screen reads it with useLocalSearchParams(), which returns id as the string '42'.

open as a page

In Expo Router, how do (group) folders and index.tsx files shape URLs, and how do you make a tab group the app's first screen?

level: middleimportance: must knowfreq 55%
basics
~20 s

A folder named in parentheses, such as (tabs), groups routes under a shared layout without adding a URL segment, and index.tsx is its folder's default route, so src/app/(tabs)/index.tsx answers / and the tab bar becomes the first screen.

open as a page

In Expo Router, how do useLocalSearchParams and useGlobalSearchParams differ when several /product/[id] screens are stacked, and which should a screen use?

level: middleimportance: must knowfreq 50%
basics
~20 s

useLocalSearchParams returns the params of the route the component belongs to and ignores other screens' URLs; useGlobalSearchParams follows the currently focused URL and re-renders background screens on every change, so screens should use the local hook.

open as a page

In an Expo project, what is a config plugin function, and how does a mod plugin like withInfoPlist change a native file?

level: juniorimportance: must knowfreq 42%
basics
~20 s

An Expo config plugin is a synchronous function that takes the app config and optional props and returns the config. A mod plugin like withInfoPlist registers an async callback that edits the parsed Info.plist during npx expo prebuild.

open as a page

In an Expo project, what does an entry in the app config's plugins array do, and when do its changes reach the installed app?

level: juniorimportance: should knowfreq 45%
basics
~20 s

Each plugins entry names a config plugin, a Node.js function that edits the Expo config and registers native file changes. Those changes are written at prebuild, so they reach users only in a new native build, never through a JavaScript update.

open as a page

In an Expo project, how do you raise the Android minSdkVersion for a maps SDK without editing build.gradle, and where does the value land?

level: middleimportance: should knowfreq 38%
basics
~10 s

Add ["expo-build-properties", { "android": { "minSdkVersion": 26 } }] to plugins and re-run prebuild. The plugin writes android.minSdkVersion into android/gradle.properties, which the generated Gradle build reads.

open as a page

In an Expo app config, why does the order of the plugins array matter when two plugins edit the same native file?

level: middleimportance: should knowfreq 30%
basics
~20 s

Plugin functions run in array order, but each mod for a file wraps the earlier ones, so at prebuild the later plugin's edit runs first and the earlier plugin's edit runs last. For the same manifest key, the plugin listed earlier wins.

open as a page

How would you write an Expo config plugin that adds an iOS App Groups entitlement and an Android manifest meta-data entry idempotently?

level: middleimportance: should knowfreq 28%
basics
~10 s

Write two plugin functions and compose them: withEntitlementsPlist adds the group to the com.apple.security.application-groups array only if missing, and withAndroidManifest uses AndroidConfig.Manifest.addMetaDataItemToMainApplication, which updates an existing entry instead of duplicating it.

open as a page

Which signing credentials does an EAS build need for Android and for iOS, and who holds them by default?

level: juniorimportance: must knowfreq 45%
basics
~20 s

Android builds are signed with a keystore (key alias and passwords); iOS builds need a distribution certificate plus an app-specific provisioning profile. By default credentialsSource is remote: EAS generates them on the first eas build and stores them encrypted on its servers.

open as a page

In EAS Build, what is a build profile in eas.json, and how does one Expo codebase yield development, preview and production builds?

level: juniorimportance: must knowfreq 50%
basics
~20 s

An EAS build profile is a named object under build in eas.json describing one kind of binary; eas build --profile <name> selects it. Keys like developmentClient, distribution, ios.simulator, env and channel make each profile's binary different.

open as a page

In an Expo project, what does eas submit do, and where does the uploaded build land on iOS and on Android by default?

level: juniorimportance: must knowfreq 55%
basics
~20 s

EAS Submit is a hosted service that uploads an existing .ipa or .aab to App Store Connect or Google Play. By default an iOS build lands in TestFlight and an Android build on the internal track; nothing goes to public review automatically.

open as a page

In EAS Build, what is the difference between the remote and local cli.appVersionSource in eas.json, and how does autoIncrement behave under each?

level: middleimportance: must knowfreq 50%
basics
~20 s

With remote, EAS servers store versionCode and buildNumber and inject them at build time; autoIncrement bumps the server counter. With local, the project files are the truth and autoIncrement edits app.json, which you must commit.

open as a page

With EAS Build, how do you make every production iOS build go to a TestFlight tester group automatically, and which submit profile does --auto-submit use?

level: middleimportance: must knowfreq 45%
basics
~20 s

Run eas build --profile production --auto-submit: each finished build is handed to EAS Submit using the submit profile with the same name as the build profile. Put ascAppId and a groups array in that profile's ios block.

open as a page

With EAS Update, how do you ship a typo fix to production over the air, and what do --channel, --branch and --message do?

level: juniorimportance: must knowfreq 50%
basics
~20 s

Fix the text, then run eas update --channel production --environment production --message "Fix typo". EAS bundles the JavaScript, uploads it to the branch production points at, and compatible production builds download it on a later launch.

open as a page

In an Expo app using expo-updates, why do users usually see a newly published EAS Update only on their second launch?

level: juniorimportance: must knowfreq 62%
basics
~20 s

By default expo-updates checks on every cold launch (checkAutomatically: ON_LOAD) but waits 0 ms for the result (fallbackToCacheTimeout: 0), so the app starts on its current bundle, downloads the update in the background and runs it on the next cold start.

open as a page

In an Expo app shipping EAS Update, what does runtimeVersion guarantee, and which changes force a new runtime version and a new build?

level: juniorimportance: must knowfreq 44%
basics
~20 s

runtimeVersion labels the native layer a build contains; EAS Update serves an update only to builds whose runtime version and platform match exactly. Any change to native code or native config needs a new build and a new runtime version.

open as a page

In EAS Update, what is the difference between a channel and a branch, and how does an installed build decide which update to run?

level: middleimportance: must knowfreq 55%
basics
~20 s

A channel is a name compiled into a build; a branch is an ordered list of published updates. Each channel points at a branch, and a build runs that branch's newest update matching its runtime version and platform.

open as a page

With expo-updates, how would you let a news app download an update mid-session and prompt readers to restart into it?

level: middleimportance: must knowfreq 48%
basics
~20 s

Call Updates.checkForUpdateAsync() at a sensible moment, then fetchUpdateAsync() if isAvailable is true; when useUpdates() reports isUpdatePending, show a restart prompt whose button calls Updates.reloadAsync(). If the reader declines, the update runs on the next cold start.

open as a page