DEV Community

Cover image for Expo SDK 58 beta opens with iOS 27 support and a faster build pipeline
Dan for Expo

Posted on Originally published at expo.dev

Expo SDK 58 beta opens with iOS 27 support and a faster build pipeline

You update your simulators, iOS 27 drops, and every app you haven't rebuilt yet either misbehaves or just looks wrong on the new scene-based life cycle. That's the situation SDK 58 is built to get ahead of.

The SDK 58 beta starts today and runs three to four weeks. It's your window to test the new release against your app's specific configuration before it goes stable, and the Expo team will be shipping fixes and improvements throughout. SDK 58 targets iOS 27 and ships React Native 0.88 (Release Candidate), since 0.88 itself hasn't been tagged stable yet. Full release notes land with the stable release, but you can already dig through the changelogs in the expo/expo repository to see the scope, and everything will get merged into the root CHANGELOG.md once the beta wraps.

For now, Expo Go on SDK 58 is available through Expo CLI for Android devices/emulators and iOS simulators, and through eas go for physical iOS devices. The App Store and Google Play builds of Expo Go will pick up SDK 58 shortly after stable ships, giving you time to migrate before the store version drops SDK 57. If you want to help test, the team is running office hours on Discord.

Here's what's in the beta.

Built for iOS 27

iOS 27 requires the UIKit scene-based life cycle and makes iPhone apps resizable by default, both of which apps built with the iOS 27 SDK inherit automatically. Expo apps now use the scene-based life cycle so they launch correctly (#46733); if you customize AppDelegate.swift, read the scene life cycle migration guide before you upgrade, since app delegate callbacks are delivered differently now. Expo packages also stopped reading geometry from UIScreen.main in favor of scene-aware helpers in expo-modules-core (#48172, #48168).

Two behavior changes worth knowing: requireFullScreen no longer opts your app out of resizing on iOS 27, and ScreenOrientation.lockAsync may not do anything while the app is resizable, see orientation locks on iOS 27. And expo-glass-effect now reports isLiquidGlassAvailable as true for apps built with the iOS 27 SDK.

EAS Build images with Xcode 27 are coming soon; until then the latest image ships Xcode 26.6.

Device Hub support. Xcode 27 replaces Simulator.app with Device Hub, and Expo CLI detects it, boots simulators through it, and focuses the right device via its devices:// deep link. If you'd rather stay in the browser, install the new dev tools plugin:

npx expo install expo-device-hub
Enter fullscreen mode Exit fullscreen mode

Run npx expo start and the link prints in your terminal. You get a live stream of every iOS simulator and Android emulator, tap/swipe/type interaction, boot/shutdown controls, CPU/memory/network graphs, and toggles for appearance, Liquid Glass, text size, and accessibility. If you don't have Xcode or Android Studio installed locally, keep an eye on EAS Simulator, a cloud simulator service in the works.

The expo-device-hub dashboard in a browser: a sidebar listing an iPhone 17 Pro Max simulator on iOS 27 and a Pixel 9 emulator, a live stream of the app in the middle, and a panel with CPU, memory, and network graphs and device options such as appearance, Liquid Glass, text size, and accessibility toggles.
expo-device-hub streaming an iPhone 17 Pro Max simulator running iOS 27. Source: Nathan Schroeder's post on X.

App Intents: hand your app to Siri

expo-app-intents exposes Apple App Intents (Siri, Shortcuts, Spotlight, Apple Intelligence) from your JavaScript (#47207). You declare intent types in Swift inline modules inside an app-intents directory, because Apple's build-time metadata extraction only reads code in the app target. The package routes invocations to JS and stores entity values. Scaffold it with npx expo-app-intents init.

In practice: an onboarding app declares a "complete task" intent and a task entity. Ask Siri to complete a task, Siri lists your app's tasks, you pick one, and the intent runs in JS and marks it done, no need to foreground the app.

Two iPhone screenshots. On the left, Siri asks 'Which task did you complete?' and lists onboarding tasks from the app such as 'Sign offer letter' and 'Upload ID documents'. On the right, Siri says 'Marking Upload ID documents as done' above the app's checklist, where that task is now checked.
An App Intent exposed from an Expo app: Siri asks which task to complete, then runs the intent in the app.

This library is in alpha, with docs and examples coming.

Expo UI: native navigation, more Compose components, actual visuals

Every component in the Expo UI reference now has a screenshot next to its API, so you can see what you're picking before reading the props. Browse the SwiftUI and Jetpack Compose galleries.

Side by side grids of Expo UI component previews from the docs: SwiftUI components for iOS on the left and Jetpack Compose components for Android on the right
A sample of the SwiftUI and Jetpack Compose component galleries in the Expo UI docs.

On iOS, @expo/ui adds NavigationStack, Toolbar, NavigationLink, NavigationDestination, the navigationTitle modifier, and a close button role, plus two and three-column layouts for NavigationSplitView. Several SwiftUI modifiers (background, tint, border, strokeBorder, foregroundStyle, and others) now accept any ShapeStyle, so you can paint a view with a material or gradient instead of just a color. On Compose, you get new Image, DateRangePicker, DateRangePickerDialog, and VerticalSlider components. Across both platforms, BottomSheet gains contentPadding, containerColor, and contentColor, and on web it drops the unmaintained vaul in favor of an in-house <dialog> implementation.

Also fixed: views hosted inside <RNHostView> are now measured where SwiftUI/Compose actually placed them, which resolves dropped presses, stolen gestures, and missing touches inside BottomSheet, Popover, AlertDialog, and DropdownMenu.

EAS Observe, now built into the SDK

EAS Observe, generally available since August 20, gets deeper expo-observe integration in SDK 58: Observe.registerIntegration, ObserveErrorBoundary and reportError, an errorHandlingEnabled option, an expo-image integration, and Observe.clientId recorded on every event. AppMetrics is deprecated in favor of Observe.

The EAS Observe overview dashboard showing active users, sessions, and a release comparison table with time to interactive, time to first render, crash-free sessions, crash-free users, errors, and affected users for three releases.
The EAS Observe overview: startup metrics, crash-free rates, and errors compared across releases.

@expo/agent-cli: a CLI meant to be driven by an agent, not you

Agents kept learning Expo the hard way: launch Expo Go, hit a runtime error on an unsupported native module, guess that you need a development build, then figure out when to reach for Expo CLI versus EAS CLI. @expo/agent-cli sits on top of both plus expo-doctor and falls back to Expo CLI, so you can just tell your agent to use it.

Useful commands: status checks Expo Go compatibility without starting anything, dev starts the app with one command (deciding whether a build is needed and picking the fastest path), smoke is dev plus a screenshot plus stop, a quick check that the project actually runs on your machine, and skills:sync sets up co-located agent skills from node_modules.

Start with:

npx @expo/agent-cli@latest agents:setup
Enter fullscreen mode Exit fullscreen mode

then tell your agent to use @expo/agent-cli in place of Expo CLI. A shorter alias, expo-agent-cli, is also available (thanks to Kazutoyo Tokai for handing over the name). This is under active development, try it and file feedback.

Home screen widgets land on Android

expo-widgets now has an Android implementation: widgets run their own JS bundle on a dedicated Hermes runtime, with interaction support and Material Colors. On iOS, Live Activities pick up a staleDate option on start()/update(), stable ActivityKit identifiers, and runtime configuration changes. See the expo-widgets docs.

Groundwork for Swift Package Manager builds

React Native 0.87 added experimental Swift Package Manager support as an alternative to CocoaPods. SDK 58 restructures Expo's iOS sources so Swift, Objective-C, and C++ compile as separate SwiftPM targets, and an Expo autolinking plugin for npx react-native spm is in review, with a preview planned during the beta. CocoaPods stays the default, and precompiled Expo modules keep working there.

Expo Modules: faster builds, faster calls, and a much simpler API

expo-modules-core now ships precompiled native libraries for Android instead of compiling libexpo-modules-core.so from source on every clean build, CI run, and EAS Build. In our test on a blank SDK 58 project, a clean Android debug build across all four ABIs dropped from about 80 seconds to about 39 seconds on an Apple M3 Pro, roughly half, with bigger savings on machines with fewer cores like most CI runners.

On the call-overhead side, SDK 56 already cut iOS calls 1.6 to 2.3x and made Android Record conversion around 6x faster by replacing reflection with a Kotlin compiler plugin. SDK 58 takes another pass at iOS: cheap calls cost less, long strings move up to 3.8x faster in either direction, and async function promises are cheaper to create and resolve. None of this needs adoption, every Expo module gets faster on SDK 58, including ones you didn't write.

The bigger change is Expo Modules 2.0, now in beta on both platforms. Instead of describing a native module inside a DSL, you write an ordinary Swift or Kotlin class and annotate what you want exposed to JS, which is also a much easier shape for coding agents to generate since there's no custom DSL to explain. Because the binding is generated at build time from those annotations instead of resolved at runtime, it's faster again: synchronous calls are 2.5 to 5.6x faster than the DSL on an iPhone 16 Pro release build. On Android, across twelve microbenchmarks, Expo Modules 2.0 beats Expo Modules 1.0 in every case (1.2x to 39.9x) and beats TurboModules in every case too, for example a no-argument call takes 112 ns versus 2,837 ns for a TurboModule.

Twelve Android microbenchmarks comparing a TurboModule, Expo Modules 1.0, and Expo Modules 2.0. Expo Modules 2.0 has the shortest bar in every benchmark, from calling a function with no arguments to emitting an event with an object.
Android microbenchmarks for Expo Modules 2.0 against Expo Modules 1.0 and a TurboModule. Lower is better.

Docs are coming during the beta; the early look post has the full benchmark and API walkthrough.

Fingerprint changes less often

Bumping a version number or touching files inside node_modules used to invalidate your fingerprint for no good reason. SDK 58 defaults fingerprint to the balanced preset: it ignores routine changes like version in app.json and hashes native modules by package name and version instead of their files. Result: fewer unnecessary native rebuilds triggered by fingerprint mismatches. See fingerprint presets if you want a different tradeoff.

An experimental Rust transformer that halves cold bundling time

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

Two transform timelines for the same app. With Noxcturnal enabled, 5,477 files finish in 2.46 seconds of wall time. With Babel, 5,494 files take 5.26 seconds.
Transform worker activity for a cold bundle, with the new transformer (top) and Babel (bottom). Source: Phil Pluckthun's post on X.

Enable it with experiments.noxcturnalTransformWorker in your app config. It only activates when your project has no custom transform worker or Babel plugins/presets, and it skips files that need the Reanimated or Worklets transforms. Numbers and behavior may still shift before stable.

Expo Router: data loaders, native tabs, and a reworked core

Data loaders, server-side rendering, middleware, native tabs, toolbars, and the standard navigation integration are all stable now, no experimental flags required. The navigation core was also reworked to make navigation state deterministic and drop 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 moved or disappeared, file an issue if something you need is missing.

Other changes: JS Tabs, TopTabs, and Drawer now share the standard-navigation integration used by NativeTabs; loaders can call setResponseHeaders({ 'Cache-Control': 'private, max-age=60' }) for client-side response caching; navigation uses React transitions to keep the current screen visible while the next loads, configurable with activityEnabled or NavigationAwareActivity; web enables asyncRoutes by default, splitting route files into separate chunks; extendRouter/extendRouterActions let you override built-in router behavior; and screen-level error boundaries are now supported via unstable_settings.screenErrorBoundary. Review the migration guide before upgrading, especially with custom navigators.

Dev servers behind proxies, tunnels, and more than one Metro instance

Development builds fetch a manifest on startup that used to hardcode absolute bundle and debugger URLs, which broke when your dev server sat behind a proxy, ran on a remote machine, or was discovered over Bonjour. The manifest now uses relative URLs when the client supports them. Separately, RCT_METRO_PORT used to be frozen at 8081 at compile time in bare apps without expo-dev-client, so two such apps running locally would answer each other's reload commands; prebuild now writes the actual port to Info.plist and the app reads it at launch.

Expo CLI also has a new tunnel built on @expo/ws-tunnel 2.0, connecting faster, holding the connection more reliably, and dropping the dependency on @expo/ngrok. Try it:

EXPO_UNSTABLE_TUNNEL_V2=1 expo start --tunnel
Enter fullscreen mode Exit fullscreen mode

You'll need to be signed in (expo login). The EXPO_UNSTABLE_TUNNEL_V2 flag goes away later in the beta once this becomes the default.

React Native 0.88 (Release Candidate)

SDK 58 beta jumps from React Native 0.86 to the 0.88 release candidate, picking up both 0.87 and 0.88-rc.0. Highlights: opt-in Swift Package Manager tooling via npx react-native spm, an object syntax for fontVariationSettings on Text (pairs nicely with expo-font's new variable font support), native ArrayBuffer representations in TurboModules on both platforms, and experimental performance screenshots plus WebSocket inspection in React Native DevTools.

PostHog, wired up in one command

Run eas integrations:posthog:connect and EAS CLI creates or links your PostHog project, installs the SDK and config plugin, and writes environment variables to your project and EAS. Every event PostHog captures now carries eas/update_id, eas/build_id, eas/channel, and eas/runtime_version, linked back to expo.dev. This is an EAS CLI feature, so it works on any SDK version. See the PostHog changelog and the using PostHog guide.

Smaller updates worth knowing about

  • expo-camera: CameraView.scanDocumentAsync for multi-page document scanning on Android and iOS.
  • expo-file-system: File.digest() for MD5/SHA-1/SHA-256/SHA-384/SHA-512, plus File.preview()/File.canPreview().
  • expo-audio: lockscreen controls with playlists, WAV/PCM recording via AudioStream.startFileRecordingAsync.
  • expo-image: transition.skipOnCacheHit, and an svgVariables prop that substitutes values for CSS custom properties in an SVG so one document can render with different colors in different places.
  • expo-secure-store: requireConfirmation on Android for authenticated reads/writes.
  • expo-dev-launcher: disableFab=1 and disableAutoLaunch=1 URL params.
  • expo: URL/URLSearchParams rewritten with IDNA/TR-46 support, TextDecoder rewritten for speed.
  • expo-font: variable font support across Android, iOS, and web, so one font file covers a whole family of weights/styles.
  • expo-notifications: coexists with other push libraries' UNUserNotificationCenterDelegate on iOS, notification grouping via threadIdentifier, an alarmClock delivery option on Android for time-critical alarms.
  • expo-location: being rewritten around a location provider abstraction with a simpler API, position watchers, and new iOS permission options. It'll ship as an opt-in preview alongside the current API, nothing changes for existing code in this beta.

Breaking changes and deprecations to plan for

The big one: React Native 0.87 makes the Strict TypeScript API the default, deep imports from react-native/Libraries/* are now type errors, and ref types changed shape. Follow the migration guide or use the migrate-to-strict-api skill; you can defer with a tsconfig.json opt-out, but it's removed after React Native 0.88.

Also worth flagging: several React Native APIs are removed (InteractionManager, the Touchable root export, NativeMethods types, Modal's animated prop, and some deprecated StatusBar props); npx expo prebuild now generates SceneDelegate.swift for the scene-based life cycle; NODE_ENV is now set explicitly by every Expo CLI command before loading env files and app config, so an inherited value no longer wins; Android release builds enable R8 by default via android.enableMinifyInReleaseBuilds=true, shrinking and optimizing your APK (opt out with expo-build-properties if a dependency needs ProGuard keep rules you haven't written yet); expo-file-system's File.write() is now async (File.writeSync() keeps the old behavior); expo-sqlite drops libSQL support entirely; and expo-notifications now shows foreground notifications by default unless your handler says otherwise. Deprecations to note: File.md5 in favor of File.digest(), NativeArrayBuffer in favor of ArrayBuffer, and AppMetrics in favor of Observe.

Full details for every item above live in the migration guide and the expo-router changelog.

Tooling requirements

Expo tooling now needs Node.js 22.13+ (22.x line), 24.3+ (24.x line), or Node 26+; odd releases like 23 and 25 aren't supported. Expo modules tooling is now compatible with Android Gradle Plugin 9.

No regressions have been reported yet; that section of the changelog will get updated as the beta progresses.

Where to start

  • New project: scaffold with create-expo-app, it'll use whichever package manager invoked it.
  • Existing project: try the expo-upgrade skill with Claude Code, or run npx expo install expo@next --fix and follow the native project upgrade helper.
  • Test the native build path: use EAS Build, or if you have Xcode/Android Studio locally, run npx expo prebuild --clean followed by npm run ios / npm run android (or just npx expo run).

If you hit a problem, open an issue with a minimal reproducible example, mention you're on the SDK 58 beta, and include a root cause if you've found one, it helps get a fix out faster.

This post is based on content from the Expo blog. Follow @expo for more React Native content.

Top comments (0)