Expo SDK 58

Alan HughesAlan Hughes
Brent VatneBrent Vatne
Expo SDK 58 is released. SDK 58 includes React Native 0.88

Today we're announcing the release of Expo SDK 58. SDK 58 is built for iOS 27 and includes React Native 0.88. Thank you to everyone who helped with beta testing.

Note: Expo Go for SDK 58 on the Apple App Store and Google Play Store will be released within a few weeks. Learn more.

SDK 58 is built for iOS 27

iOS 27 requires the UIKit scene-based life cycle and makes iPhone apps resizable, and apps built with the iOS 27 SDK get both automatically. Expo apps use the scene-based life cycle so they launch correctly on iOS 27 (#46733). This changes how app delegate callbacks are delivered, so read the scene life cycle migration guide if you customize AppDelegate.swift. Expo packages no longer read geometry from UIScreen.main (#48172, #48315, #48316, #48170, #48169), and expo-modules-core has scene-aware geometry helpers (#48168).

On iOS 27, requireFullScreen no longer opts your app out of resizing, and ScreenOrientation.lockAsync may have no effect while the app is resizable. The requireFullScreen config plugin is updated for this (#48175). See Orientation locks on iOS 27 and later. expo-glass-effect reports isLiquidGlassAvailable as true in apps built with the iOS 27 SDK (#49850).

iPhone Duo support

Apps built with SDK 58 are ready for iPhone Duo on the day it is released. On iPhone Duo, iOS moves most bars and controls into a vertical rail on the right or left edge. Your app will also need to respond to changes in window size as the device is folded or an app is shown in split view on the inner display. Start with the basics:

  • Navigation: Use Expo Router's Stack and NativeTabs, or SwiftUI views through Expo UI's <Host>. They use the system's own navigation and tab bars, which iOS places for you on both displays.
  • Safe areas: Safe area handling becomes more important, and you may need to add <SafeAreaView> in more places, such as inside scrollviews, to handle horizontal insets for iPhone Duo. Update react-native-safe-area-context to the latest version via npx expo install react-native-safe-area-context.

Expo Go on iPhone Duo, folded (left) and open (right).

Module authors who present popovers can use the new SceneGeometry.anchorPopover helper in expo-modules-core to anchor them correctly on any regular-width display (#50433).

For layout, Expo UI adds ArrangementView, the SwiftUI container Apple recommends for iPhone Duo. It places a primary and a secondary view side by side or as an overlay, and adapts to the fold and the device pose.

To react to the hinge itself, use the new expo-hinge package. useHinge(), getHinge(), and addHingeListener() report the hinge angle and whether it is closed, partially open, or fully open, on iOS 27.1 and later and on Android 11 foldables. In SwiftUI, the onHingeChange modifier does the same for a single view, and a callback from useWorkletCallback runs on the UI thread to keep effects such as parallax smooth. Apple recommends hinge state for interactions and effects, such as a tabletop camera shutter, not for layout.

Device Hub support for Xcode 27

Xcode 27 replaces Simulator.app with Device Hub. Expo CLI detects Device Hub, boots simulators through it, and focuses the right device using its devices:// deep link (#46757, #46809). If you'd rather not leave your editor, the new expo-device-hub dev tools plugin turns your browser into a local device dashboard: a live stream of every iOS simulator and Android emulator that you can tap, swipe, and type into; boot and shutdown controls; CPU, memory, and network graphs; and toggles for appearance, Liquid Glass, text size, and accessibility settings. Install it with npx expo install expo-device-hub, run npx expo start, and the link prints in your terminal. If you want the same experience without a local Xcode or Android Studio install, keep an eye on EAS Simulator, our upcoming service for cloud simulators.

expo-device-hub streaming an iPhone 17 Pro Max simulator running iOS 27. Source: Nathan Schroeder's post on X.

App Intents: expose your app to Siri and Apple Intelligence

expo-app-intents exposes your app’s actions and content to Siri, Shortcuts, Spotlight, and Apple Intelligence. Declare intents in Swift inline modules in an app-intents directory so Apple can discover them in the app target at build time. The package delivers invocations to JavaScript and stores entity data you update from JavaScript. Run npx expo-app-intents init to set up app-intents in your app. If you use coding agents, run npx @expo/agent-cli@latest install expo-app-intents to install the agent skill. Learn more about agent skill setup. The library is in alpha.

The package also supports Spotlight indexing, intent donations for system suggestions, and identifying on-screen content with appEntityIdentifier() for Expo UI or AppEntityView for React Native. On-screen identification requires iOS 18.4 or later and Xcode 27 or later.

A version coming soon will support running expo-task-manager tasks from intent handlers, so you can perform work in JavaScript and return a result to Siri without opening your app.

Example: completing a task from Siri

For example, an onboarding checklist app can let Siri list its tasks and ask which one to complete.

An App Intent exposed from an Expo app: Siri asks which task to complete, then runs the intent in the app.

Expo UI: recycling lists, native navigation on iOS, new Compose components, and more

Recycling lists for large data sets

List, LazyVStack and LazyHStack on iOS, and LazyColumn and LazyRow on Android now virtualize and recycle rows (#49975, #50579, #50258). Instead of rendering every row up front, they render only the rows near the visible area and reuse them as you scroll.

Pass the items as data, a unique key for each item with keyExtractor, and a children function that returns the row. Use List.ForEach, LazyVStack.ForEach and LazyHStack.ForEach in SwiftUI, and LazyColumn.Items and LazyRow.Items in Jetpack Compose.

See the best practices for large lists in SwiftUI and Jetpack Compose before you switch.

Every component now has a visual in the docs

The Expo UI reference now shows a screenshot of every component next to its API, so you can see what you're picking before you read the props. Browse the SwiftUI and Jetpack Compose galleries in the docs.

A sample of the SwiftUI and Jetpack Compose component galleries in the Expo UI docs.

iOS navigation. @expo/ui adds NavigationStack, Toolbar, NavigationLink and NavigationDestination components, the navigationTitle modifier, and the close button role (#49940, #49991). NavigationSplitView adds two- and three-column layouts (#50075), and ToolbarItem takes placement and visibilityPriority (#50360).

SwiftUI. The background, tint, border, strokeBorder, containerBackground, presentationBackground and foregroundStyle modifiers now accept any ShapeStyle, so a view can be painted with a material or a gradient (#48905, #49621, #49767, #49838). Also new: the Background component (#48904) and the scrollClipDisabled (#49780), geometryGroup (#48838), listRowSeparatorTint (#48810), alignmentGuide (#49247), menuStyle and menuIndicator (#48448), and scrollEdgeEffectStyle (#50367) modifiers.

Jetpack Compose. New Image (#48521), DateRangePicker, DateRangePickerDialog (#49788) and VerticalSlider (#49612) components. DropdownMenu gains cornerRadius and shadowElevation (#49515, #49896), and RadioButton gains enabled and colors (#49852).

Universal components. BottomSheet adds contentPadding, containerColor and contentColor, and ListItem takes a colors prop (#48902, #49575). On web, BottomSheet drops the unmaintained vaul for an in-house HTML <dialog> (#49509).

Hosted React Native views. Views inside <RNHostView> are now measured where SwiftUI and Jetpack Compose place them, so measure() returns correct values and touches work inside BottomSheet, Popover, AlertDialog and DropdownMenu (#48969, #48970, #50715, #50727). Tapping outside a hosted text field dismisses the keyboard (#48788), and closing a SwiftUI Menu no longer presses the view underneath it (#48463, #49775).

Expo Observe

Expo Observe monitors your app in production. It records startup and navigation performance, JavaScript errors, and native crashes from your users' devices, and compares each release with all your releases so you can catch regressions early. In SDK 58, expo-observe adds an errorHandlingEnabled option to turn off automatic recording of unhandled JavaScript errors (#48506) and exposes Observe.clientId so you can match Observe data with other tools (#49599). AppMetrics is deprecated in favor of Observe; see Deprecations.

The Expo Observe overview: startup metrics, crash-free rates, and errors compared across releases.

Agentic development with Expo CLIs

Expo CLI now has updated help text and new commands that work better with agent harnesses. You may see your agent invoking npx expo command:ps and npx expo command:events, which give it machine-readable information about running Expo processes and their event logs. This helps it understand the state of its own and other Expo dev servers. These commands are experimental. We'll expand them over time and adjust their output to better support automated use of Expo CLI.

We've used this system throughout SDK 58's development, and it has also helped us profile, debug, and improve Expo CLI internally. We've observed that giving agents awareness of all running Expo commands and their events helps them use Expo CLI more reliably and consistently. This replaces our old debug logging, which means you'll see a change in output when you debug Expo CLI yourself with EXPO_DEBUG=1.

However, these improvements haven't been enough on their own, so we're also introducing a CLI primarily aimed at agentic development.

@expo/agent-cli: the new CLI designed for agents

Agents used to learn Expo the hard way: launch Expo Go and hit a runtime error if you add a native module Expo Go doesn't support, then guess that they need a development build. They also had to know when to use Expo CLI and when to use EAS CLI. @expo/agent-cli is a new experimental CLI, built by agents and designed for agents. It sits on top of Expo CLI, EAS CLI, and expo-doctor, and falls back to Expo CLI, so you can ask your agent to use it as a replacement.

Some useful commands you can try: status answers Expo Go compatibility without starting the app. dev starts the app with one command, like a web framework starting a server and a browser. It takes care of when to build the app and tries the fastest way to get your app running. Commands print next steps, so agents know how to move on. smoke is dev, plus a screenshot, plus stop: it checks that you can actually start developing the app on your machine. skills:sync sets up co-located agent skills from node_modules in your project.

Start with npx @expo/agent-cli@latest agents:setup, then tell your agent to use @expo/agent-cli instead of Expo CLI.

If you prefer a shorter name, expo-agent-cli is available as an alias. Thanks to Kazutoyo Tokai for transferring the package name to us. This CLI is under active development, and we'll go into more detail in an upcoming blog post. In the meantime, try it out and let us know what you think.

Home screen widgets come to Android

expo-widgets adds an Android implementation: widgets run their own JS bundle on a dedicated Hermes runtime, with support for interactions and Material Colors (#46961, #47035, #48454). Android widgets also support the cornerRadius modifier (#50333). On iOS, Live Activities gain a staleDate option on LiveActivity.start() and update() (#46343), stable ActivityKit identifiers (#48589), and runtime widget configuration changes (#47533).

Swift Package Manager builds

React Native 0.87 added experimental Swift Package Manager support as an alternative to CocoaPods, and SDK 58 lays the groundwork for Expo apps to use it. Expo's iOS sources have been restructured so their Swift, Objective-C, and C++ compile as separate SwiftPM targets (#45906, #49820, #49821, #49832), and an Expo autolinking plugin for React Native's npx react-native spm pipeline is available as a preview (#49823). CocoaPods remains the default and supported path, and precompiled Expo modules keep working there.

Expo Modules: faster builds, faster calls, simpler to write

Faster Android builds with prebuilt expo-modules-core

expo-modules-core now ships precompiled native libraries for Android. Previously, every clean build, CI run, and EAS Build compiled the C++ in libexpo-modules-core.so from source, the most expensive step of an Android build that uses Expo. The npm package now includes .so files for all four ABIs in debug and release variants. In our test on a blank SDK 58 project, a clean Android debug build for all four ABIs took about 80 seconds on an Apple M3 Pro. Skipping the expo-modules-core C++ compilation cut it to about 39 seconds, roughly half. The saving is larger on machines with fewer cores, such as CI runners. The removed compiler work adds up to over 10 minutes across the four ABIs, which a 12-core machine hides by running many files in parallel. To compile the libraries from source instead, set android.buildExpoModulesCoreFromSource with expo-build-properties (#50452).

Faster Expo modules

A native module call isn't free. There's a fixed cost to crossing into Swift or Kotlin and converting the values that go each way, and on the cheap calls that's most of what you're paying for. SDK 56 went after this on both platforms: we dropped the Objective-C++ layer on iOS to make calls 1.6 to 2.3× faster, and replaced reflection with a Kotlin compiler plugin on Android, making Record conversion around 6× faster and cutting time to first render by roughly 30%.

SDK 58 takes another pass at the iOS side. Calls that pass or return simple values are cheaper (#49761), passing long strings in either direction is up to 3.8× faster (#49678), and promises from async functions cost less to create and resolve (#49718). There's nothing to adopt: update to SDK 58 and every Expo module gets faster, including the ones you didn't write.

Expo Modules 2.0 beta

The other half of the story is the API you write against. Instead of describing your native module inside the existing DSL definition, you can now write an ordinary Swift or Kotlin class and annotate the methods and properties you want to expose to JavaScript. There's nothing new to learn beyond a handful of annotations. It's a much better fit for coding agents too, since there's no custom DSL to explain first. It's just Swift and Kotlin, which models already know well.

It's faster again on top of the improvements above, since the annotations let us generate the most direct binding at build time instead of figuring one out at runtime. On the same SDK, synchronous calls are 2.5 to 5.6× faster than the DSL, measured on an iPhone 16 Pro in a Release build. An early look at Expo Modules 2.0 has the benchmark and covers the API in more depth.

On Android, the Kotlin side shows the same pattern, and this time we also measured against React Native's TurboModules. Across twelve microbenchmarks, Expo Modules 2.0 is faster than Expo Modules 1.0 in every case, from 1.2× for passing an array of 100 numbers to 39.9× for emitting an event with a number. It is also faster than a TurboModule in every case: a call with no arguments takes 112 ns against 2,837 ns, passing an array of 20 objects takes 10.7 µs against 149.8 µs, and emitting an event with an object takes 963 ns against 8,080 ns.

Android microbenchmarks for Expo Modules 2.0 against Expo Modules 1.0 and a TurboModule. Lower is better. The green labels compare 2.0 with 1.0.

SDK 57 shipped the iOS macros as an experiment. With SDK 58, Expo Modules 2.0 is in beta on both iOS and Android. The early look blog post is the best guide until the documentation is available.

Fewer unnecessary native rebuilds with the new fingerprint default

Bumping a version, or changing files inside node_modules, no longer means a new fingerprint. SDK 58 defaults fingerprint to the balanced preset (#47668). It ignores common cases like updating version in app.json, and hashes native modules by package name and version instead of their files and directories. The hash stays stable without extra config. Learn more about fingerprint presets if you want a different tradeoff.

Experimental: 2× faster cold bundling with a Rust-based transformer

Metro's cache keeps warm bundling fast, but cold bundles, whether a full project or a changed module, spend most of their time in Babel. SDK 58 introduces an experimental replacement: "Noxcturnal", a module transformer written in Rust on top of oxc and exposed to Node.js through napi-rs. In our internal testing, per-module transform time was about 3× faster and end-to-end cold bundling about 2× faster (#48443). On our largest test app, a cold bundle of roughly 5,500 files went from 5.26 seconds with Babel to 2.46 seconds with the new transformer, with 443 files still falling back to Babel.

Transform worker activity for a cold bundle of our largest test app, with the new transformer (top) and with Babel (bottom). Source: Phil Pluckthun's post on X.

Try it by setting experiments.noxcturnalTransformWorker in your app config. It only activates when your project has no custom transform worker and no custom Babel plugins or presets, and it skips files that need the Reanimated or Worklets Babel transforms. This is early work: the implementation and the numbers may change before it's stable, and we plan to add configuration options so that more projects, including those with custom Babel transforms, can use it.

Expo Router: stable data loaders and native tabs, improved core

Data loaders, server-side rendering, and middleware are now stable and no longer need experimental flags. Native tabs, toolbars, and the standard navigation integration are also stable.

Expo Router's navigation core has also been reworked so that navigation state is deterministic and easier to reason about, and it no longer carries most of the forked React Navigation API. Most apps won't notice. If you built a custom navigator or import from expo-router/react-navigation, some APIs have moved or been removed. If one you need is missing, please open an issue and describe what you're trying to do.

Other changes are listed below.

  • Loader caching: loader responses now rely on the platform HTTP cache. Set a Cache-Control header in your loader to control whether the client reuses cached data. See the data loaders guide for more information.
  • Server output by default: Expo Router projects on SDK 58 use web.output: "server" when web.output is not set, and server output now renders HTML on each request instead of prerendering it during export. To keep the SDK 57 behavior, set web.output to "static" and enable the new apiRoutes option of the expo-router config plugin. See Review server rendering behavior.
  • Async routes on web by default: the expo-router config plugin now enables async routes for web automatically. Route files are split into separate JavaScript chunks, which the browser loads when navigating to those routes. Set asyncRoutes: { web: false } to disable this.
  • React transitions: navigation can now use React transitions to keep the current screen visible while the next one loads and to schedule preloads in the background. By default, only preload operations use transitions. See the navigation transitions guide to configure when transitions are used.
  • React activities: set activityEnabled on a navigator or screen, or wrap part of a screen in NavigationAwareActivity, to hide inactive content while preserving its state and cleaning up its effects.
  • Custom router API: use extendRouter to override parts of a built-in router, or extendRouterActions to add handlers for custom navigation actions.
  • Screen error boundaries: set unstable_settings.screenErrorBoundary in a layout or pass <Stack unstable_screenErrorBoundary={MyErrorBoundary} /> to show fallback UI for a screen that throws, while keeping the navigator mounted.
  • Safe areas on web: Expo Router no longer provides safe-area insets on web through react-native-safe-area-context. Use CSS env(safe-area-inset-*) values instead.
  • Optional dependencies: on Android, install expo-symbols for NativeTabs.Trigger.Icon with the md prop and @expo/ui for Stack.Toolbar. These packages are now optional peer dependencies.
  • Analytics API: Expo Router now provides an experimental PerformanceObserver API for third-party analytics integrations. See the screen tracking guide to learn how to track focused screens.

Review the migration guide when upgrading, especially if you use custom navigators or import from expo-router/react-navigation.

Connecting to dev servers through proxies and tunnels

If you run your dev server behind a proxy, on a remote machine, or over a tunnel, development builds should now connect to it reliably. Previously, the manifest a development build fetched on startup told it which bundle URL and debugger hostname to use, and those could point at an address the client couldn't reach when the server sat behind a proxy or was discovered over Bonjour. The manifest now uses relative URLs when the client supports them (#47255, #48997).

RCT_METRO_PORT was a compile-time macro frozen at 8081 in prebuilt React Native, so bare apps without expo-dev-client always talked to port 8081, and two such apps running at once answered each other's reload commands. Prebuild now writes the port to the RCTMetroPort key in Info.plist, which the app reads at launch (#48098). Expo CLI port selection is also more consistent, and non-interactive runs roll over to the next free port instead of failing (#47771, #48300).

A faster, more reliable Expo CLI tunnel

Expo CLI has a new tunnel implementation built on @expo/ws-tunnel (#46696). It connects faster, holds the connection more reliably, and no longer needs @expo/ngrok. This is now the default when running expo start with a tunnel:

You'll need to be signed in to your Expo account (expo login) to use it. You can still use the legacy @expo/ngrok tunnels by passing --tunnel ngrok.

React Native 0.88

SDK 58 upgrades React Native from 0.86 to 0.88, so it picks up the changes from both React Native 0.87 and the React Native 0.88 release candidate. Highlights of 0.88 include:

  • Swift Package Manager tooling: the opt-in npx react-native spm command and SwiftPM package generation. CocoaPods remains the default and supported path.
  • fontVariationSettings on Text: an object syntax for setting variable font axes, which pairs well with the variable font support added to expo-font in this release.
  • ArrayBuffer in TurboModules: JavaScript ArrayBuffer values now have native representations on both Android and iOS, with an explicit byte-ownership contract.
  • React Native DevTools: experimental performance screenshots, and WebSocket inspection in the Network panel.

Refer to the React Native 0.87 release notes and the React Native 0.88.0-rc.0 release notes for the full picture.

PostHog integration

You can now set up PostHog in an Expo app with one command, and every event it captures knows which EAS update it came from. Run eas integrations:posthog:connect and EAS CLI creates or links your PostHog project, installs the SDK and its config plugin, and writes the environment variables to your project and to EAS. PostHog recognizes the eas/update_id, eas/build_id, eas/channel, and eas/runtime_version properties and links them back to expo.dev. This is an EAS CLI feature, so it works with any SDK version. Read the PostHog integration changelog and the Using PostHog guide.

Launch URLs for development builds and Expo Go

Development builds and Expo Go now share one launch URL contract. Any query parameter that starts with __expo_ is a launcher command, on any host, so launch options no longer collide with your app's own deep links (#50287, #50288). The launcher handles these parameters and removes them before your app receives the URL:

  • __expo_url: the URL-encoded project or update URL to load.
  • __expo_disable_onboarding=1: skips the onboarding screen that appears on the first launch after installation.
  • __expo_disable_fab=1: hides the Tools button (the floating button that opens the dev menu) for that launch only.
  • __expo_disable_auto_launch=1: prevents the dev menu from opening when the app starts, for that launch only.

npx expo start now prints {scheme}://?__expo_url={manifestUrl} instead of {scheme}://expo-development-client/?url={manifestUrl}. The legacy form and its parameters keep working, so existing links, QR codes, and scripts are unaffected (#49651, #50290). For CI, end-to-end tests, and agent loops, set EXPO_NO_DEV_MENU=1 to add the three __expo_disable_* parameters to every launch URL and QR code that Expo CLI creates. Expo CLI does this by default when it is not interactive; set EXPO_NO_DEV_MENU=0 to opt out (Expo CLI environment variables).

expo-linking and Expo Router ignore the reserved parameters, so they never show up as route search params, and expo-linking exports unwrapDevLaunchURL() to resolve the URL an app should route from a launch URL. Linking.parse() also stops decoding query values twice, so percent-encoded %, +, and @ come through unchanged (#50289). See Deep linking to an update's URL for the full list of parameters.

Other notable changes

  • expo-camera: added CameraView.scanDocumentAsync for multi-page document scanning on Android and iOS (#47362).
  • expo-file-system: added File.digest() for MD5/SHA-1/SHA-256/SHA-384/SHA-512 hashes (#48089), and File.preview() and File.canPreview() for native preview flows.
  • expo-audio: added lock screen controls with playlists (#46020) and WAV/PCM file recording with AudioStream.startFileRecordingAsync (#46771).
  • expo-image: added transition.skipOnCacheHit to skip transitions for cached images (#48181), and an svgVariables prop that substitutes values for the CSS custom properties an SVG source refers to with var(), so different parts of one document can be given different colors while the image stays a vector (#48663). Values are not limited to colors. SVG sources that use var() now render with their fallback values even without the prop.
  • expo-secure-store: added requireConfirmation on Android for authenticated reads and writes (#48556).
  • expo: URL and URLSearchParams were rewritten with IDNA/TR-46 support (#47813), and TextDecoder was rewritten for speed (#48877).
  • expo-sqlite: ships a co-located agent skill that shares best practices with agents. Run npx @expo/agent-cli@latest install expo-sqlite to set it up in your project (#49867). Also added interruptSync() to cancel running operations on a database connection (#50123). To add full-text search (FTS) support on web, use the new configureWeb() method to load your own wa-sqlite WebAssembly build. Learn more about configureWeb().
  • expo-font: added support for variable fonts across Android, iOS, and web, so a single font file can provide a family with several weights or styles. This works with useFonts, loadAsync, and the config plugin (#48129, #48621, #48432, #49486, #49487, #49485). Learn more.
  • expo-notifications: on iOS, notification center calls are forwarded to a UNUserNotificationCenterDelegate registered by another library, so expo-notifications and another push library can coexist in the same app (#48313), and notifications can be grouped with threadIdentifier (#49429). On Android, a new delivery: 'alarmClock' option on date and repeating triggers uses AlarmManager.setAlarmClock(), which OEM battery policies don't defer, for time-critical alarms (#49687), and the config plugin gained a largeIcon property (#49481). Learn more.
  • expo-location: you can now try a new expo-location API, available under expo-location/next. It is built around a location provider abstraction, with a simpler position API, position watchers, background location updates, a separate location foreground service on Android, and new permission options such as requesting temporary full accuracy on iOS (#49849, #49972, #49988). We'd love to hear your feedback. Keep an eye on the changelog as we continue to backport new features to SDK 58. Separately, geofencing on iOS no longer requires the location background mode, which could get apps rejected under App Store guideline 2.5.4 when no feature uses persistent location (#50197).
  • @expo/metro-config: inlined EXPO_PUBLIC_* environment variables are now recorded in the transform cache, so changing a value invalidates the cached output instead of leaving the old value in your bundle (#47750). Stack frames from node_modules are no longer collapsed, so an error thrown inside a library shows where it was thrown (#50973).
  • @expo/log-box: LogBox now shows the call stack and code frame of an error even when every frame is collapsed, instead of an empty stack (#50976).
  • @expo/cli: Android Virtual Device (AVD) and adb management has been rewritten to be more resilient to failures (#49258). A failed npx expo run:ios now prints the Xcode build log path and no longer drops Xcode errors from its output (#48624, expo/third-party#162). The terminal now shows the call stack of a thrown error even when all of its frames are collapsed (#50975).
  • expo-updates: added the updates.maxUpdatesToKeep config to set how many updates are stored on the device. If you use channel surfing to switch channels often, a larger value avoids downloading the same updates again each time. Learn more about updates.maxUpdatesToKeep.

Deprecations

  • expo-file-system: File.md5 is deprecated in favor of File.digest() (#48089).
  • expo-modules-core: NativeArrayBuffer is deprecated in favor of ArrayBuffer (#47106).
  • expo-observe: the AppMetrics export is deprecated in favor of Observe (#48901).
  • expo-router: withLayoutContext is deprecated in favor of integrateWithRouter (#50337).
  • expo-sqlite: the useLibSQL config plugin property is deprecated and no longer has any effect (#48543).
  • @expo/ui: on iOS, backgroundOverlay is deprecated in favor of background (#49621), and the color parameter of border and strokeBorder is deprecated in favor of content (#49838). Passing elements as List.ForEach children is deprecated in favor of data, keyExtractor and a children function (#49975). On Android, the HorizontalCenteredHeroCarousel maxItemWidth prop is deprecated in favor of preferredItemWidth, and the five TimePicker color names that contain Unselected (such as clockDialUnselectedContentColor) are deprecated in favor of the Material 3 names without it (such as clockDialContentColor) (#51426).

Notable breaking changes

  • React Native Strict TypeScript API: React Native 0.87 makes the Strict TypeScript API the default. Deep imports from react-native/Libraries/* are now type errors, and some types changed shape, most visibly refs, which now use instance types such as ViewInstance and TextInputInstance. Follow the migration guide, or hand it to an agent with the migrate-to-strict-api skill. To defer the migration, set "customConditions": ["react-native", "react-native-legacy-deep-imports"] in tsconfig.json (keep "react-native", which expo/tsconfig.base already sets). This opt-out is set to be removed after React Native 0.89, so plan to migrate during the SDK 58 cycle.
  • React Native API removals: InteractionManager (use requestIdleCallback), the Touchable root export, the NativeMethods types, the Modal animated prop, and the deprecated StatusBar backgroundColor, translucent, and networkActivityIndicatorVisible props are removed. ImageBackground is deprecated in favor of a View with an absolutely positioned Image. See the 0.87 and 0.88.0-rc.0 release notes for the full list.
  • macOS: SDK 58 does not support macOS yet. Support will return when react-native-macos is updated to at least React Native 0.86.
  • iOS scene-based life cycle: the iOS 27 SDK requires the UIScene life cycle, so npx expo prebuild now generates SceneDelegate.swift and a UIApplicationSceneManifest entry in Info.plist, and the UIWindow is created by the scene delegate instead of in didFinishLaunchingWithOptions (#46733, #46734). Expo forwards URL, user activity, Handoff, life cycle, and quick action events to ExpoAppDelegate overrides and subscribers, so config plugins and modules built on those keep working (#49925, #50032). If you manage the ios directory by hand, have a bare React Native app, or override app delegate methods directly, see the migration guide.
  • NODE_ENV and environment files: Expo CLI now sets NODE_ENV to development or production based on the command you run and replaces any other value, so .env files and dynamic app configs always load in that mode (#48554, #48741). For example, NODE_ENV=test npx expo start now loads .env.development instead of .env.test. Debug builds use development, and every other build, including custom configurations like Staging, uses production (#49511, #50661, #49452, #49555, #50204). On EAS Build, expo config, expo prebuild and expo-doctor use production (#48938, #48845). See the environment variables guide.
  • Android app template: R8 is now enabled by default in release builds through android.enableMinifyInReleaseBuilds=true in gradle.properties (#50108). R8 shrinks, optimizes, and obfuscates Java/Kotlin code, which reduces app size and improves startup time. Apps are optimized with the proguard-android-optimize.txt preset. Libraries that rely on reflection may need ProGuard keep rules. To opt out, set android.enableMinifyInReleaseBuilds to false with expo-build-properties.
  • expo-router: the navigation core changes make navigation states and route keys deterministic, defer navigation dispatch until after commit, and remove a large portion of the forked React Navigation API surface. @expo/ui and expo-symbols are now optional peer dependencies. Router projects now default to server output, which renders HTML on each request. The migration guide covers each change, and the expo-router changelog has the full list.
  • expo-file-system: File.write() is now async and returns a promise; use File.writeSync() for the previous behavior (#45992).
  • expo-sqlite: libSQL support is removed — syncLibSQL() and libSQLOptions are gone (#48543).
  • expo-notifications: foreground notifications are now shown by default unless your setNotificationHandler says otherwise, and a slow handler no longer drops the notification (#49072). See Other notable changes for the other expo-notifications changes in this release.
  • @expo/fingerprint: the default preset is now balanced (#47668).
  • @expo/ui: on iOS, <Host> top-aligns its content instead of centering it, so a flex: 1 host matches Android (#47561). HStack, VStack and GlassEffectContainer now forward an unset spacing to SwiftUI as nil and use the system default spacing; pass spacing={0} to keep the previous layout (#48441). On Android, the style prop is removed from RNHostView; use modifiers instead (#48970).
  • expo-localization: on iOS, RTL is no longer force-enabled from the device locale with supportsRTL; layout direction follows I18nManager (#48086).
  • expo-media-library: on iOS, Asset.getUri() and AssetInfo.uri now resolve the current Photos version of a video; pass version: AssetUriVersion.ORIGINAL for the previous behavior (#48640).
  • @expo/cli: browser-based login is now the default for expo login; use --no-browser or --username/--password for the previous behavior (#46832). expo serve now refuses to start on an export made with expo export --dev that includes server code (#50522). Export again without --dev.

Tool version bumps

  • Node.js: Expo tooling now requires Node.js 22.13 or newer on the 22 line, 24.3 or newer on the 24 line, or Node.js 26 and later. Odd-numbered releases such as 23 and 25 are not supported (#47202).
  • Android Gradle Plugin: Expo modules tooling is now compatible with AGP 9 (#46766, #46769).
  • Jetpack Compose: @expo/ui and the Expo module template now use Compose UI and Foundation 1.12.1 (from 1.10.6) and Material 3 1.5.0-beta01 (from 1.5.0-alpha17) (#51426). There are no breaking changes. A few props are renamed to match Material 3, and the old names still work (see Deprecations).

Expo Go

Reminder: The Expo Go app is our tool for getting started quickly; it's an educational tool to help you learn to build on mobile. If you are past that stage and want to build and ship an app, we encourage you to migrate your project to using a development build.

The new Expo Go will be released on the App Store and Google Play within a few weeks. Until then, npx create-expo-app creates SDK 57 projects. To start a new SDK 58 project, run npx create-expo-app --template default@sdk-58 and use a development build. The new Expo Go will support only SDK 58, so upgrade your project to keep using Expo Go from the stores.

Upgrading your app

Try using our expo-upgrade skill (installation instructions) with Claude Code, or a similar tool of your choice, to upgrade your app.

Here's how to upgrade your app to Expo SDK 58 from 57:

  • Upgrade all dependencies to match SDK 58:
  • Check for any possible known issues with Expo Doctor:

Thanks to everyone who contributed to the release!

Thanks to the Expo team — everyone contributed one way or another.

Thanks to Aziz Becha, who passed the expo-hinge name on npm to us and contributed its Android support (#51201).

External contributors: Aaron Leopold, Aayush Gautam, Adam Kapos, Adam Zvada, ahmadaccino, AHMED, Ahmed Sbai, Albert Martin, Aldo Ryanda, Alec Molloy, Alex Rapp, Alexis CHAPPRON, Alfonso Curbelo, Alireza Hadjar, Alok Debnath, Amane INOUE, Ammar Ahmed, Anatoliy Magda, Andrea Verrecchia, Andrei, Andrzej Wielski, Andy Matuschak, Aonan Li, Aron, Artem, Aziz Becha, Bao Nguyen, Batuhan Demir, Benjamin Komen, Brahmananda Swain, Brian Jeong, capt-muji, cdm, Changin OH, Charles Burgess, chinesepowered, Christopher Wallace, ciospettw, ClarityStorm, Colin McDonnell, crockalet, Dan Kanefsky, dancer13, Daniel Avellaneda, Dave Matthews, David, David Cornejo, Davide Bertoni, Denis LE, Denys Hrychulevych, Devauntae Norman, Dillon Montag, Dimitris Tzimikas, Dirk Postma, Drew Miller, Duy Anh, Eddie Xie, Elehiggle, Eliot Gevers, Emma Granados, enkdevur, fallmo, Felipe Castor, Frederic_Bui, Gautam Kumar, Gauvain, Gaëtan Kueny, Giovanne Tarcitano, Gregor MacLennan, Gregory Moskaliuk, Hakan Akın, Harry Yu, Henrique Barone, Henry J, Huangshuo Kuang, Hubert Bieszczad, Hugo Extrat, huhu415, Ido Yana, Ihor Sviziev, invivek26, Irfan fandi, Jacint Varga, jakequade-pc, Jakov Glavina, Jakub Hanko, Janic Duplessis, Jason, Jason Safaiyeh, JB, jensdev, Jiahao Lu, Jiun Shin, Joao Paulo Costa Marra, Joel Kanzelmeyer, Johan Kasperi, Jonas Rosengren, Jonathan Grimes, josefnorlin-svt, Jozef Harag, K.Dileepa Thushan Peiris, Kamronbek_Juraev, Kazutoyo Tokai, Keven Leone, Kevin, Kevin van der Burg, Kirill Zyusko, Kota, Krzysztof Piaskowy, Kræn Hansen, Kurogoma4D, Kyle Asaff, Lasya Appali, Leonardo Cavalcante, Leonardo E. Dominguez, Lizzi Lindboe, M.M, Mad Dinh, Maher Kurdi, MangelSpec, Marc Shilling, Martin Treurnicht, Mateusz Łachacz, Matthew Chisolm, Matthew Lieder, Matthieu Gicquel, Max Karadeniz, Max Lapides, Maykon Michel Palma, Md Asadujjaman Bappi, Michael Chow, Michał Pierzchała, Min Hu, Mina Sameh, misersavingsinc-cpu, Mitsunao Miyamoto, mplumador, Mr. Thee, MUSE, Nicola Corti, Nikhil Vashistha, Niklas Grieger, Nuno Caseiro, Oleg Bezrukavnikov, Olivier Allard, Oscar Nord, padmeshjakhmola, Pieter Willekens, pipizhu, Prabin Silwal, Qiyang, Radek Czemerys, Ramon Claudio, RasmusKard, René Klomp, Rio Rafelino, Robert Soriano, Rocco Vaccone, Roitium., Roman Diukachov, Rutvik Nabhoya, Ryan Lucia, Ryan Saffer, Samran Asif, Samuel Corsan, Samuel Newman, Satyajit Sahoo, Sean Bullock, seaxng, sebholl, Self Not Found, Serhii Lizunov, Seydi Charyyev, Shane Lord, Shawn Thye, Shubh Porwal, Simon Eldevig, SJvaca30, Skylar Barrera, soreavis, Soroh Daukariebi Michael, stareezy, Stathis Ntonas, Stephen Saucier, Steve South, stumbaumr, stvrmrz, Sunny Doshi, Svend, Taha Koçal, Tera, Thibault Malbranche, ThomasConway01, Tim, Tim Lenahan, Tomasz Zawadzki, Tomasz Żelawski, Trần Đình Huy, tsushanth, Tuomas Lannes, Usmanov Samandar, Valentin Nagacevschi, Victor Perez, Vincent Ong, Vivek JM, VoulgarisLeoni, xxih, yathavan, Yousef Abu Shanab, and 이성현.

Beta testers: Alexey, Carlos Espinoza, Eliot Gevers, Frank Manno, Godwin Rose Samuel, Irek Róg, Jakub Hanko, Javier Rubio, Jon Love, Jörn Röder, Kevin Detter, Laurent Spitaels, Liangzhen Zhu, Marcin Nowak, marco242424, Matthew Horan, Mike Olson, Moisés Neto, Nick McMillan, Nikos, Rúben Ferreira, Taha Koçal, Thibault Malbranche, Vincent Guillebaud, and Yasser's studio.