Expo app.json Config Reference

All Expo app.json / app.config.js fields with platform scope and type.

Searchable Expo application configuration reference for app.json and app.config.js — name, slug, version, icon, splash, ios, android and web fields — with platform scope, value type and what each field controls. It runs free in your browser on Gera Tools, with nothing uploaded.

Last updated Source: Gera Tools

What is the difference between name and slug?

name is the human-readable display name of your app, while slug is a URL-friendly identifier used by Expo and EAS to reference the project. The slug should be lowercase with hyphens and stay stable across builds.

Expo app.json configuration

Expo apps are configured by an app.json file (or a dynamic app.config.js) whose expo object drives the build, store metadata and native settings. Some fields are shared across platforms; others live under ios, android or web. This searchable reference lists the key fields with their platform scope and type so you can configure builds and submissions correctly.

How it works

Static config lives under the top-level expo key:

{
  "expo": {
    "name": "My App",
    "slug": "my-app",
    "version": "2.1.0",
    "icon": "./assets/icon.png",
    "ios": { "bundleIdentifier": "com.example.myapp", "buildNumber": "57" },
    "android": { "package": "com.example.myapp", "versionCode": 57 },
    "runtimeVersion": { "policy": "appVersion" }
  }
}

For dynamic values, export from app.config.js instead and read process.env. EAS uses slug plus ios.bundleIdentifier / android.package to identify and submit the app, and runtimeVersion to gate OTA updates.

Key fields explained

Shared fields (all platforms)

name — The display name that appears under the app icon on the home screen and in the App Store / Google Play listing. Keep it short; Apple recommends under 30 characters.

slug — A lowercase, URL-safe identifier used internally by Expo and EAS. It anchors your project on expo.dev — changing it moves the EAS project to a new entity, breaking update channels and build history. Treat it as permanent once set.

version — The human-readable semantic version string (e.g. "2.1.0"). This is what users see in the store. Increment it with every user-visible release.

icon — Path to the 1024×1024 PNG icon used as the source for all platform-specific sizes. EAS and the Expo build pipeline generate the correct sizes for App Store, Google Play, adaptive icons, and notifications from this single source.

splash — The launch screen shown while the JS bundle loads. Specify image, backgroundColor, and resizeMode. On iOS, the splash is rendered with native resizeMode; on Android it is drawn in the centre of the screen.

iOS-specific fields (ios.*)

ios.bundleIdentifier — The unique reverse-DNS identifier for the iOS app (e.g. com.example.myapp). Registered on the Apple Developer portal. Cannot be changed after the first App Store submission without creating a new app entry. EAS uses this to match the correct provisioning profile.

ios.buildNumber — An integer or string that Apple requires to increment on every TestFlight or App Store upload within a given version. Format as a plain integer string ("57"). EAS Build can auto-increment this.

ios.infoPlist — A dictionary of raw Info.plist entries. Use this for keys that Expo does not expose directly — camera usage descriptions, background fetch modes, associated domains for Universal Links.

Android-specific fields (android.*)

android.package — The Android application ID (e.g. com.example.myapp). Equivalent to ios.bundleIdentifier. Once published to Google Play, it is permanent for that listing.

android.versionCode — A positive integer that must increase with every APK or AAB upload to Google Play. Unlike version, this is invisible to users but enforced by the Play Store.

android.permissions — Array of Android manifest permission strings. Expo’s managed workflow includes a default set; list only what your app actually needs. Unnecessary permissions trigger Play Store policy review.

OTA updates

runtimeVersion — Tells EAS Update which native runtime is compatible with an OTA bundle. The most common value is { "policy": "appVersion" }, meaning only devices running the same version can receive the update. Mismatching a bundle to an incompatible native layer causes startup crashes.

Common mistakes

  • Forgetting to bump buildNumber / versionCode before uploading to the store. Both stores reject uploads where the internal counter has not increased, even when version changed.
  • Changing slug mid-project. This silently creates a new EAS project on expo.dev, orphaning existing update channels. If you must rename, transfer the old project ID manually.
  • Using app.json for env-dependent values. A static JSON file bakes values at the time the config is read, not at runtime. Move to app.config.js and read process.env if you need different values across build profiles (development / staging / production).
  • Omitting ios.infoPlist usage descriptions. Apple rejects submissions that access protected APIs (camera, microphone, location) without a human-readable usage description string in Info.plist.

Tips

  • Keep slug stable; changing it points EAS at a different project.
  • Bump ios.buildNumber and android.versionCode on every store upload.
  • Use runtimeVersion so OTA updates only reach compatible native builds.
  • Set native bits via ios.infoPlist, android.permissions or config plugins.
  • Validate your config with npx expo config --type public before building — it prints the resolved config including any dynamic values from app.config.js.