releases.shpreview
Expo/Expo Changelog

Expo Changelog

Mon
Wed
Fri
JunJulAugSepOctNovDecJanFebMarAprMay
Less
More
Releases10Avg3/mo

Connect your AI coding assistant to your Expo project from any Expo plan.

The Expo MCP Server is now available on the Free plan, with monthly MCP usage included for Free accounts.

When we first launched the Expo MCP Server, it was available only on paid plans. Now anyone with an Expo account can connect an AI coding assistant to Expo docs and tools.

What you can do with Expo MCP

MCP (Model Context Protocol) is an open standard that lets AI assistants connect to external tools and project context. We introduced the Expo MCP Server in Become an AI-native developer with the Expo MCP Server; this update makes that workflow available to anyone with an Expo account.

Developers are already using Expo MCP for a few common workflows:

  • Finding the right Expo answer faster. Your assistant can pull in official Expo documentation and SDK guidance while you are editing code.
  • Debugging builds and workflows. It can inspect EAS build status, workflow runs, logs, failures, and related TestFlight crashes or feedback.
  • Verifying app behavior locally. With a local dev server and simulator or emulator, it can take screenshots, tap through flows, inspect views, and collect logs.

Included Free-plan usage

Free accounts include monthly MCP usage intended to cover individual development, evaluation, prototypes, and occasional Expo-specific assistant help.

Usage is counted at the billing account level. If your account belongs to an organization, all members share the same included monthly usage. If the account reaches its monthly limit, MCP requests will fail with an error until usage is available again.

For heavier MCP usage, paid plans include higher usage limits and may include access to newer MCP capabilities before they are broadly available.

Get started and feedback

Follow the MCP docs to connect your AI coding assistant to Expo. MCP gives your agent access to Expo tools and project context; Expo Skills give it Expo-specific instructions for building, deploying, and debugging apps.

We'd like to hear what you want MCP and Skills to help with next. Checking whether a release is ready? Understanding update rollout health? Turning build, workflow, or TestFlight signals into concrete fixes? Find us in Discord or on X.

Today we're announcing the release of Expo SDK 56. SDK 56 includes React Native 0.85 and React 19.2. Thank you to everyone who helped with beta testing.

Expo UI is now ready for production

As of SDK 56, the Jetpack Compose (Android) and SwiftUI (iOS) APIs in Expo UI are stable. We've added Expo UI to the default create-expo-app template, so new Expo apps can use a rich set of native UI primitives right out of the box, and Expo UI is now available in Expo Go.

This milestone release builds on three SDK cycles of iteration across SDK 53, 54, and 55. Thank you to everyone on the Expo team and in the community who helped test, audit, and refine the APIs from the original SwiftUI prototype to the Jetpack Compose implementation.

SDK 56 focuses on three core pieces of Expo UI: a new universal components API for shared interfaces, stable native APIs, and drop-in replacements for popular React Native community libraries.

Universal components

Expo UI now includes universal components that work across Android, iOS, and web. Unlike the Android and iOS APIs, the web APIs are still experimental and likely to change.

The universal components are backed by @expo/ui/jetpack-compose on Android, @expo/ui/swift-ui on iOS, and react-dom or react-native-web on web. You can now build more cross-platform UI with Expo UI without splitting files into .android.tsx and .ios.tsx.

Universal components include layout primitives, text, inputs, controls, and sheets such as Host, Row, Column, ScrollView, Text, TextInput, Button, Switch, Slider, Checkbox, and BottomSheet. Learn more.

Stable native APIs

Expo UI's SwiftUI and Jetpack Compose APIs are now stable after several rounds of breaking changes. These changes align Expo UI more closely with the underlying frameworks, so developers and coding agents can lean on native platform documentation and examples directly when writing Expo UI code.

  • Extend Expo UI with custom views and modifiers: you can now extend Expo UI with your own SwiftUI and Jetpack Compose views and modifiers. Expo UI manages layout synchronization, props, and events for you. See the guides for examples: SwiftUI guide and Compose guide.
  • Material 3 Dynamic Colors and the Material Symbols catalog: the new useMaterialColors hook hands you Material 3 Dynamic Colors that follow the system theme, and the Icon component pairs with @expo/material-symbols to bring the full Material Symbols catalog within import reach.
  • react-native-worklets integration and native state: Expo UI now integrates with react-native-worklets and native state primitives from the underlying UI frameworks: ObservableObject on SwiftUI and MutableState on Jetpack Compose. The new useNativeState hook lets JavaScript control that native state directly, which is useful for native state-driven animations and form controls. Learn more about useNativeState for SwiftUI and Jetpack Compose.
  • Synchronous worklet callbacks: a new WorkletCallback shared object allows synchronous UI worklet callbacks to be passed as props to Expo UI views on both platforms. TextField on iOS and Compose can now use native state for value, and onValueChange accepts WorkletCallback, enabling synchronous, flicker-free controlled text inputs.
  • Components, modifiers, and API changes: SDK 56 lands the bulk of the stabilization work since SDK 55. See the @expo/ui CHANGELOG for the full list.
Drop-in replacements for community components

Expo UI is focused on native primitives, and some of those primitives overlap with popular community libraries. To make migration easier and reduce library fragmentation, SDK 56 introduces drop-in replacements for several common community components.

For example, you can migrate from:

import DateTimePicker from '@react-native-community/datetimepicker';

to:

import DateTimePicker from '@expo/ui/community/datetime-picker';

Drop-in replacements are available for @gorhom/bottom-sheet, @react-native-community/datetimepicker, @react-native-masked-view/masked-view, @react-native-menu/menu, react-native-pager-view, @react-native-picker/picker, @react-native-segmented-control/segmented-control, and @react-native-community/slider APIs. Most migrations only require changing the import, though some props may be unsupported or differ because Expo UI is backed by SwiftUI and Jetpack Compose rather than UIKit and Android Views. Learn more.

Faster native builds

Precompiled Expo packages on iOS

SDK 56 ships prebuilt XCFrameworks for our most complex Expo modules on iOS, to speed up your iOS builds. In our measurements, this cuts median clean iOS build times by around 1 minute (~16%) — both locally and on EAS Build. This is enabled by default both locally and on EAS Build — no configuration required. To opt out, set the EXPO_USE_PRECOMPILED_MODULES environment variable to 0 (for local builds), and also as an EAS environment variable (for EAS Build).

Precompiled headers for Android codegen (experimental)

A new opt-in android.usePrecompiledHeaders option in expo-build-properties applies CMake precompiled headers to the C++ codegen output for every autolinked native module, dramatically cutting CMake compile times on Android. In our benchmarks, the :app:buildCMakeDebug task dropped from 17m 10s to 6m 06s — a 2.81x speedup. In a default new project, builds are about 1.3x faster. Results will vary by project, but the larger your autolinked module graph, the bigger the win.

Enable it in app.json:

{
  "plugins": [
    ["expo-build-properties", { "android": { "usePrecompiledHeaders": true } }]
  ]
}

This feature is experimental in SDK 56 while we gather feedback, and we're working on upstreaming it to React Native so every app benefits from faster Android builds. Learn more in the PR.

Expo Modules: easier to write, faster to run

Inline modules

Starting with SDK 56, you can now define Expo modules directly within your project structure, alongside your JavaScript and TypeScript code. We call these inline modules, and they make experimenting with native code easier than ever.

After setting up your app to use inline modules, you can open Kotlin and Swift files and write your Expo modules with no additional setup. During prebuild, the iOS Xcode project is updated and the necessary options are set in the Android project, which lets us add inline modules to the build and autolink them automatically.

With the newly released type generation tools, which offer a few CLI commands tailored towards inline modules, you will have an even smoother experience. You can just create a Swift inline module and a CLI watcher will automatically generate a TypeScript interface for it right beside the Swift file. The TypeScript interface is separated into a generated and stable part, so that you have control over your stable TS interface and leave the generated part to be regenerated on any changes. If you want more control you can always generate these files manually.

You can develop inline modules from Android Studio, Xcode, or any other IDE as they are part of your project structure.

Check out the inline modules reference and the tutorial for more information!

Type generation tools

In SDK 56 we introduce a powerful new tool for developing Expo Modules. The new expo-type-information package exports functions that parse and retrieve type information from a Swift Expo module and ones that generate TypeScript interface from the retrieved information.

It also includes a CLI with the following key commands:

  • module-interface: takes a Swift Expo module (accepting multiple file paths or a path to the module root) and generates multiple TypeScript files based on our standard interface scheme:
    • [ModuleName]Types.ts: contains all type declarations.
    • [ModuleName]Module.ts: contains the module class.
    • [ModuleName]View.tsx: exports the default view component(s) with typed props.
    • index.ts: re-exports the module alongside every defined type and view.
  • inline-modules-interface: generates a pair of TypeScript files (generated, stable) for each Swift inline module in a project.
  • short-module-interface: works similarly to inline-modules-interface, but targets a specific Swift module instead of all inline modules in the project.

All of these commands can be run in a watch mode to automatically regenerate the TypeScript interfaces.

Learn more in the expo-type-information reference and tutorial.

Revamped create-expo-module

In SDK 56, create-expo-module has been revamped for improved stability and a richer feature-set.

  • New create-expo-module skill: helps agents create Expo modules — coming soon.
  • New addPlatformSupport subcommand: adds support for additional platforms in an existing module — for example, adding Android support to an iOS-only module. The command detects the features currently used in your module and scaffolds the native files for you.
  • Modular template: when creating a module you can pick which features get scaffolded and which platforms it targets.
  • Non-interactive mode support: field defaults have been improved and some fields are no longer required; in non-interactive mode, create-expo-module logs the defaults that were used.
  • No barrel file by default: local modules no longer use index.ts; pass --barrel to opt in.
  • Windows support: create-expo-module now works well on Windows.
Runtime performance improvements in expo-modules-core

Kotlin compiler plugin — A new Kotlin compiler plugin replaces reflection with build-time code generation for Expo Modules on Android. In our benchmarks, we're seeing roughly 40% faster cold starts and 33% faster first render, with no app-side changes required. By collecting module metadata at compile time rather than runtime, we eliminate the reflection-based function-type-to-converter mapping that has historically been a major speed bump for Expo Modules on Android. Results will vary by project, but this is only the beginning — this compiler-driven approach lets us optimize function invocation directly, starting with a noticeable speed boost for Record conversion in SDK 56.

New JSI layer for iOS native modules — Until now, calling into a native module from JavaScript on iOS meant crossing three language boundaries: Swift, Objective-C++, and C++. In SDK 56 we removed the Objective-C++ middle layer entirely by adopting Swift/C++ interop to talk to JSI directly. Fewer hops means less call overhead, and in our benchmarks we're seeing significant performance improvements across native module calls. The codebase is also significantly easier to work with now that it is Swift all the way down. We'll cover the architecture, benchmarks, and what this enables in an in-depth blog post.

React Native 0.85 and React 19.2

Expo SDK 55 included React Native 0.83, so be sure to refer to the full release notes for 0.84 and 0.85 for the complete picture. A few highlights include:

  • Hermes v1 by default: Hermes v1 is now the default JavaScript engine, bringing faster startup times, improved runtime performance, and reduced memory usage. You can opt out with the useHermesV1 configuration in expo-build-properties.
  • New animation backend: React Native 0.85 introduces a new animation backend designed to better align with the New Architecture, improving consistency and performance of animations across platforms.
  • HTTPS dev server: the Metro dev server now supports HTTPS via TLS configuration, enabling secure local development environments and compatibility with APIs that require secure origins.
  • Node.js minimum bump: React Native 0.85 drops support for Node.js versions before v20.19.4.

Hermes bytecode diffing is now enabled by default

In SDK 55 we introduced opt-in Hermes bytecode diffing for expo-updates and EAS Update: instead of downloading a full bundle on every update, the client downloads a binary patch against the previously installed bytecode. In the 24 hours before this post went out, EAS Update served diffed Hermes bundles that were on average 58% smaller than the full bundle they replaced.

Diffing is on by default in SDK 56. To opt out, set "enableBsdiffPatchSupport": false in the updates block of app.json. Learn more in the expo-updates API reference.

We're also working on extending bytecode diffing to patch against the embedded bundle shipped in your native build, not just against the previously installed update. This will give the first update after a fresh install the same size savings that subsequent updates already see. We're planning to ship this as an opt-in feature in an SDK 56 patch release in the coming months.

More capable expo-file-system

SDK 56 fills several parity gaps in the new expo-file-system API that became the default in SDK 54. File.downloadFileAsync() now reports progress and supports AbortSignal, and copy/move operations accept an overwrite option.

The new API also adds task-based upload and download APIs: file.createUploadTask() and File.createDownloadTask(). These bring back support for long-running transfers from the legacy file-system module, including upload progress, cancellation, and resumable downloads. For simpler uploads, File.upload() provides a convenience wrapper when you do not need to manage an upload task directly.

File picking is more capable now too: File.pickFileAsync() supports selecting multiple files and multiple MIME types, bringing it closer to expo-document-picker feature parity. We also fixed several correctness and reliability issues, including large-file md5 hashing memory usage, Android SAF copy/move support, and totalDiskSpace reporting on iOS.

We've also added experimental file-system event watching with File.watch() and Directory.watch(), which will let apps subscribe to file and directory changes without polling.

Status bar and navigation bar APIs are now consistent

Both expo-status-bar and expo-navigation-bar now expose a React component with the same prop surface, where multiple instances merge in mount order. To make that possible, we added a new <NavigationBar> component:

import { StatusBar } from 'expo-status-bar';
import { NavigationBar } from 'expo-navigation-bar';

const App = () => {
  useEffect(() => {
    // Imperative API
    StatusBar.setStyle('auto');
    StatusBar.setHidden(false);

    NavigationBar.setStyle('auto');
    NavigationBar.setHidden(false);
  }, []);

  return (
    <>
      {/* Declarative API: equivalent to the imperative calls above, with multiple instances merging in mount order */}
      <StatusBar style="auto" hidden={false} />
      <NavigationBar style="auto" hidden={false} />
      ...
    </>
  );
};

We also added a config plugin for expo-status-bar, and both packages' plugin options now align:

{
  "plugins": [
    [
      "expo-status-bar",
      {
        "style": "auto",
        "hidden": false
      }
    ],
    [
      "expo-navigation-bar",
      {
        "style": "auto",
        "hidden": false
      }
    ]
  ]
}

New Calendar, Contacts, and MediaLibrary APIs are now stable

With the release of Expo SDK 56, the next versions of the expo-calendar, expo-media-library, and expo-contacts libraries are officially promoted to stable.

The updated APIs have been redesigned with an object-oriented approach. Items like media assets or individual contacts are now represented as classes, which unlocks new features and makes them much easier to work with. Key improvements include granular data fetching (instead of loading entire, heavy objects at once, you can now fetch the specific properties you need) and cleaner querying and filtering using the Builder pattern.

For more technical details and usage examples on the new MediaLibrary and Contacts APIs, check out the blog post.

Widgets for iOS promoted to stable

After introducing an alpha version of Expo Widgets for iOS in SDK 55, we gathered feedback and made many fixes and improvements, and the library is now stable. In SDK 56, Widgets and Live Activities have full access to the environment and no longer need to be pre-rendered. We also improved timeline management, error handling, the config plugin, and the render timeline.

AI-friendly project scaffolding

  • Agent-ready scaffolding: new projects include AGENTS.md, CLAUDE.md, and .claude/settings.json with Expo-specific guidance.
  • Official Expo Skills for AI agents: install in Claude Code with /plugin marketplace add expo/skills followed by /plugin install expo. For Codex, Cursor, or any other agent, run npx skills add expo/skills. See the docs for per-tool setup details.

Convex integration

EAS now provisions and links Convex backends for you. Run eas integrations:convex:connect in your project and we'll install convex, create (or reuse) a Convex team linked to your EAS account, set up a project with a dev deployment, and write CONVEX_DEPLOY_KEY and EXPO_PUBLIC_CONVEX_URL to your .env.local. We also create EXPO_PUBLIC_CONVEX_URL as an EAS environment variable across Production, Preview, and Development so EAS Build picks it up automatically.

Expo CLI

SDK 56 ships the first wave of performance improvements across the whole bundling and run pipeline, with more landing in future releases.

  • Faster CLI: Various performance metrics of the Expo CLI have been improved in SDK 56
  • On-demand Filesystem: eliminates watchFolders as a load-bearing configuration option. Enabled by default; disable it by adding experiment.onDemandFilesystem: false to your app.json. Learn more in the PR.
  • Native Node.js watcher by default: rather than Watchman, we now use a native Node.js watcher and crawler by default. You can switch back to Watchman with resolver.useWatchman in a Metro config, but it's no longer recommended.
  • TypeScript 6 support and TypeScript 7 readiness: we replaced our TypeScript resolution to support TS 6 and prepare for TS 7. This resolves some monorepo bugs with tsconfig.json's paths config (#44791, #45227).
  • import.meta support: now enabled automatically (#44239).
  • Hermes v1 transforms: fewer bundler transforms are enabled for Hermes, which reduces bundling times overall (#45263, #45345).

With the On-demand Filesystem, you can also now try Expo with global virtual stores (such as in Bun and pnpm), which deduplicates installed Node modules across projects (saving ~300 MB per duplicate Expo install) and speeds up installs for agents working across multiple Git worktrees.

Type-safe config plugins

Every Expo package that ships a config plugin now exports it with full TypeScript types. Import the plugin from expo-<name>/plugin into your app.config.ts to get autocomplete, JSDoc, and deprecation hints for plugin options without leaving your editor.

Additionally, config plugins are now loaded with the same module loader that configs themselves use. This means you'll now also be able to reference local .ts files in your plugins list, or write config plugins with .mjs or .cjs extensions.

Expo Router

Expo goes way back with React Navigation: @brentvatne led the project for the 1.0 and 2.0 releases, working together with @ericvicenti and @satya164, and we helped to grow it from an idea, to a conceptual merging of React Native's old NavigationExperimental and ex-navigation, to the standard navigation library in the React Native ecosystem (now one of the standards, alongside Expo Router).

Today, React Navigation is in Satya's great hands, and our focus in the navigation space has shifted towards Expo Router. We spoke with Satya and agreed that the best path forward for both projects was for Expo Router to fork the parts of React Navigation that it builds around. You can, of course, continue to use React Navigation in your Expo projects if you find that you prefer it (try it out: npx create-expo-app@latest --template react-navigation/template). Both libraries build upon react-native-screens and have different takes on the developer experience, with Expo Router preferring file system-based routing. We expect that each library will continue to push the other forward in the future, and that the best ideas will continue to flow between them.

Now that expo-router no longer depends on react-navigation, most code imported directly from @react-navigation/* packages will no longer work out of the box alongside expo-router.

Run the codemod to handle most of the migration automatically (replace [your-source-directory] with your source folder, e.g. src or app):

npx expo@latest codemod react-navigation-to-expo-router [your-source-directory]

See the migration guide for full details, including manual migration steps.

New features
  • On Android, we've added experimental support for a toolbar. You can try it using the same API available on iOS: Stack.Toolbar.
  • In collaboration with react-native-screens, we've also introduced experimental support for a new version of the native stack (Stack v5), including initial support for Material-style headers and predictive back gesture.
  • For Expo on the Web, we now support streaming SSR when using the unstable_useServerRendering flag. As part of this change, we've also introduced a new generateMetadata function for retrieving and setting metadata on initial page load. The existing <Head> component can still be used for updating metadata after hydration. Let us know how it works for you!
  • We've also added two new helpers for data loaders: createStaticLoader and createServerLoader, which narrow the callback signature for each rendering mode. createStaticLoader receives only route params (no request), while createServerLoader always passes a request and throws an actionable error if mistakenly used during static generation.
  • We've also added the ability to customize the default <Suspense> fallbacks in a _layout route, giving you more control over loading states across your app. Following the same convention as ErrorBoundary, you can export a SuspenseFallback that receives route parameters, making it easy to show loading UI:
export function SuspenseFallback() {
  return <Loading />;
}

Brownfield: more flexibility for embedded Expo apps

SDK 56 builds on the brownfield foundation we shipped in SDK 55, with three meaningful additions for teams embedding Expo into existing native apps.

Multiple isolated apps in one host. A new experimental option lets one host app contain multiple inner expo-brownfield apps. Opt in by setting multipleFrameworks: true on the iOS plugin config, and each framework gets a unique Swift module name plus an auto-applied ObjC symbol prefix across its entire pod dependency graph, so two brownfield apps can ship side by side without colliding.

Custom Turbo Modules from the host app. Host apps can now register their own turbo module classes with the inner Expo app's React Native runtime, by passing a turboModuleClasses dictionary into ReactNativeHostManager.initialize. This makes it much easier to expose host-app capabilities (auth, app-specific SDKs, native UI controllers) to JavaScript without modifying the inner app's bundle.

iOS prebuilds by default. expo-brownfield now uses prebuilt React Native frameworks on iOS out of the box, which substantially cuts brownfield build times. If you need to opt back into building React Native from source, use the new buildReactNativeFromSource plugin option.

Expo Application Services (EAS)

Build time statistics for xcodebuild and Gradle

EAS Build now surfaces per-step timing for xcodebuild and Gradle, so you can see exactly where your native build time is spent and decide what to optimize first.

Prebuilt artifacts for major community libraries

Building on the precompiled Expo packages work in SDK 56, EAS Build now precompiles some of the most commonly used community libraries in the React Native ecosystem too — like react-native-reanimated and react-native-screens. In our measurements, this cuts median iOS clean build times on EAS Build by another ~1 minute (~20%) on top of the Expo-modules precompile, with bigger savings for apps that use more of these libraries.

Coming soon: EAS Observe

We're working on EAS Observe, a production performance monitoring service for Expo apps that tracks real-world metrics on your users' devices. Compare metrics across releases to catch regressions early, then investigate detailed session data (on your own, or hand it off to an LLM)

We've made two changes to how Expo Go loads projects. First, when accessing updates published to EAS Update in Expo Go, you can now only load projects that you own or that are owned by an organization you are a member of. This change applies to all versions of Expo Go and took effect on May 12, 2026. Second, loading Hermes bytecode bundles (HBC) is only supported for EAS Update in Expo Go, and self-hosted updates must serve plain JavaScript bundles. This change applies to the latest versions of Expo Go for SDK 54, 55, and 56.

When distributing your app for review by your team, we recommend using the official store testing tracks, such as Google Play internal testing and TestFlight; internal distribution with EAS Build or any similar service that provides ad-hoc or enterprise provisioning support and archive hosting; or development builds in combination with EAS Update. Learn more in "Overview of distributing apps for review".

Enable Gradle caching for Android builds on EAS to reuse task outputs between builds. Set EAS_GRADLE_CACHE=1 and see up to 50% faster build times.

You can now use your GitHub account to sign up or log in to Expo. And you may have received an email about it. That email from Github is legit.

MAR 4, 2026

We're building Expo Observe, a new way to understand how your app performs in the real world.

The problem

You ship your app, but do you know how fast it actually launches for your users? What about users on older devices or slower networks? When you push an update, does performance improve or regress? Did adding all those new libraries slow down your app?

These questions are hard to answer without dedicated tooling, and existing solutions often require significant setup or don't integrate well with the React Native ecosystem.

Debugging performance in React Native has been a long-standing difficulty: a challenge the React Native team have recently addressed with the new DevTools and Performance panel, but these are development-time tools. With Observe, we focus on tracking performance in production.

What Expo Observe does

In this initial Preview release, we've focused on tracking startup metrics for your app:

  • Cold Launch time: launch time when the app's native code and resources are not in memory
  • Warm Launch time: launch time when native libraries, app's state and resources are already cached in memory
  • Time to Interactive: when your app is ready for user input
  • Time to first render: when content first appears on screen
  • Bundle load time: how long it takes to load your JavaScript

How it works

Once integrated, Observe collects these metrics from real user sessions and gives you tools to understand them:

  • Track across releases: compare performance between builds and OTA updates to catch regressions early
  • Statistical breakdowns: view median, average, min, max, and percentile values (P90, P99) for each metric
  • Investigate outliers: when something looks off, dig into individual sessions to see all events, device info, and metadata for that user

What's next

This is just the beginning. We're starting with startup metrics because they're critical to user experience and retention, but we have plans to expand Observe's capabilities based on what we learn from early testers.

We're looking for developers to help us test and shape Expo Observe. If you're interested in early access, please join the waitlist.

Date: March 3, 2026
Author: Dan Kelly

Overview

You may have received (or will soon receive) an email from GitHub asking you to approve updated permissions for the Expo GitHub App. This is a legitimate request from Expo.

Action required: Click approve when you see the GitHub email.

Why we're requesting new permissions

Expo is building new features for the Expo GitHub App, and GitHub's permission model requires apps to request expanded access before using new API capabilities. Expo needs "Repository Administration" access to enable automatic EAS project setup for new and existing repositories.

Specific feature details will be shared as they roll out.

What the Expo GitHub App does today

The Expo GitHub App connects GitHub repositories to EAS. When installed and linked to an Expo project, it enables:

  • Triggering EAS Workflows from pushes, pull requests, and tags
  • Posting workflow results (like build and update links) as GitHub PR comments
  • Reading your repository contents to run builds and updates on EAS

These existing features will continue to work regardless of whether you approve the new permissions.

What happens if you don't approve

Nothing breaks. If you choose not to approve the updated permissions, your current EAS Workflows, builds, and updates will continue to function normally. You can also set up a new Expo account, connect it to an existing GitHub project, and run workflows without issue.

The only limitation is access to new agentic features as they become available. Permissions can be approved later when ready.

A note for enterprise teams

Organizations with strict access policies should know:

  • The new permissions are additive. They don't change how existing features work or what data they access.
  • You can leave them off. Every current integration (EAS Workflows, builds, updates, PR comments) works without the new permissions.
  • You can approve later. There's no deadline. Evaluate new features when they ship and approve at that point.

For questions about specific permission scopes, contact security@expo.dev or Discord.

The page shows an error message indicating that the requested release notes content for Expo SDK 55 could not be loaded properly. The actual release notes content is not available.

FEB 6, 2026 | By Mustafa Abban

Expo MCP Server now includes tools that allow your AI assistants to interact directly with your EAS Builds and Workflows. These tools enable you to manage builds, trigger workflows, and automate debugging issues, all through interacting with your favorite AI-assisted development tools like Claude Code, Cursor, and VS Code.

What's New

Build Management Tools

You can now ask your AI assistant to:

  • build_info Check build status: "Get the status of my latest iOS build"
  • build_list List builds: "What are my recent builds"
  • build_logs View build logs: "Show me the logs for the failed build"
  • build_run Trigger new builds: "Run a production build for iOS"
  • build_cancel Cancel running builds: "Cancel the build that is currently in progress"
  • build_submit Submit to app stores: "Submit the latest build to the App Store"
Workflow Automation Tools

Manage your entire workflow pipeline through conversational prompts:

  • workflow_create Create workflows: "Create a workflow for building and deploying"
  • workflow_info Monitor runs: "Get the status of the latest workflow run"
  • workflow_list List workflows: "What are my recent workflows"
  • workflow_logs View logs: "Show me the logs for the build job in the workflow"
  • workflow_run Trigger runs: "Run the build-and-deploy workflow"
  • workflow_validate Validate configurations: "Validate my workflow file"
  • workflow_cancel Cancel workflows: "Cancel the running workflow"

Examples

Retrieve recent workflow runs and investigate the root cause behind their failures:

> what are my recent workflows

Here are your recent EAS workflows:

| Date         | Workflow             | Status      | Branch | Commit         |
|--------------|----------------------|-------------|--------|----------------|
| Feb 4, 2026  | Android Build        | ✅ SUCCESS  | main   | 186dbaa - "ex" |
| Jan 30, 2026 | iOS Build and Submit | ❌ FAILURE  | main   | 186dbaa - "ex" |
| Jan 28, 2026 | iOS Build and Submit | ❌ FAILURE  | main   | 186dbaa - "ex" |
| Jan 28, 2026 | Android Build        | ⚪ CANCELED | main   | 186dbaa - "ex" |

Example scenario with interacting with build tools to inspect and rerun a build:

> what are the logs of my recent build

Here's a summary of the logs for your most recent Android build:

Build Summary
- Build ID: 60e2fdf1-fc7a-4b08-89c3-fe53bb2ea311
- Status: ✅ SUCCESS

Build Environment
- Machine: AMD, 4 vCPUs, 16 GB RAM
- Image: ubuntu-22.04-jdk-17-ndk-r26b
- Node.js: 20.19.2
- Java: 17
- Bun: 1.2.4
- NDK: 26.1.10909125

Build Phases
| Phase                      | Result                |
|----------------------------|-----------------------|
| QUEUE                      | ✅ success            |
| SPIN_UP_BUILDER            | ✅ success            |
| RUN_GRADLEW                | ✅ success (14m 55s)  |
| UPLOAD_APPLICATION_ARCHIVE | ✅ success            |
| SAVE_CACHE                 | ✅ success            |

Output
- APK Size: 81.1 MB
- Location: app-release.apk
- Gradle Tasks: 862 executed, 1 up-to-date (863 total)

Cache Stats
- Cache Hit Rate: 25% (216/864 hits)
- Cache Size: 0.04 GB / 5.00 GB

Getting Started

To use these tools, you'll need:

  • An EAS paid plan
  • The Expo MCP Server configured in your AI-assisted tool
  • An authenticated connection to your Expo account

Learn more in the MCP documentation.

JAN 26, 2026

By Mustafa Abban and Stanisław Chmiela

Compiler cache for builds

EAS supports build caching, which can speed up subsequent builds by up to 30% on both Android and iOS builds. Build caching is available to all users at no additional cost, whether you're on a free or paid plan.

Builds on EAS now support saving and restoring compiler caches with ccache. ccache is a compiler cache that speeds up recompilation by caching the results of C/C++ compilation. The saved cache will store shared output between builds such as .o files, compilation results for native libraries used by React Native, Hermes, or other native modules, and more.

From initial results on successful cache hits, we've seen up to 30% build speed increases on both Android and iOS builds and plan to improve it further.

The following environment variables control caching behavior:

  • EAS_USE_CACHE: Enables saving and restoring cache on build jobs. By setting to 1, it will enable both saving and restoring the cache during a build job. By setting to 0, it will disable both saving and restoring the cache
  • EAS_RESTORE_CACHE: Controls restoring the cache at the beginning of a build. Set to 1 to enable or 0 to disable.
  • EAS_SAVE_CACHE: Controls whether to save the build cache at the end of a build. Set to 1 to enable or 0 to disable.

Get started

Prerequisites

  • The remote compiler cache requires projects on SDK 53+ for Android and SDK 54+ for iOS.

Steps

  1. Navigate to the project's EAS environment variables settings page.
  2. Create a new variable named EAS_USE_CACHE with a value of 1. Then select every environment, typically: production, preview, and development.
  3. Kick off multiple builds. The first one will save the cache and subsequent builds will use that cache during their build process.

The cache key uses lock files to create a unique key based on your dependencies. When dependencies change, a new cache will be created while still allowing fallback to previous caches using a key prefix.

You'll also see both save and restore-cache steps appear in your build job, along with performance stats on the hit rate of the cache.

Examples

Setting the environment variables will automatically add the cache steps to your builds. You can also add this to your custom builds and workflows by setting the restore step before the build step and respective save step after.

Workflows

Use functions eas/restore_build_cache and eas/save_build_cache to apply ccache automatically.

- uses: eas/restore_build_cache
- uses: eas/build
- uses: eas/save_build_cache

Functions eas/restore_cache and eas/save_cache allow for using the service for other cache purposes. Users can define their own key pattern and file path. Here is an example of using them for ccache, equivalent to the functions above.

- uses: eas/restore_cache
  with:
    key: android-ccache-${{ hashFiles('package-lock.json') }}
    restore_keys: android
    path: /home/expo/.cache/ccache
- uses: eas/build
- uses: eas/save_cache
  with:
    key: android-ccache-${{ hashFiles('package-lock.json') }}
    path: /home/expo/.cache/ccache

Custom build

build:
  name: Build with ccache
  steps:
    - eas/checkout
    - eas/restore_build_cache
    - eas/build
    - eas/save_build_cache

Cache restrictions

Access restrictions provide cache isolation and security by creating logical boundaries between different Git branches or users. It is important for security of yours and your users to understand and leverage them when building your app.

Caching from GitHub Builds

When a build is run from GitHub, caches get scoped to the branch the build is running from. A build can restore caches created in:

  • The current branch
  • The default branch (main or master)

If a build doesn't restore a user-scoped cache, it will automatically fallback to restoring caches published from GitHub builds triggered on the default branch. This allows builds to benefit from caches created by trusted sources even when no user-scoped cache exists yet.

Caching from CLI

When a build is triggered from eas-cli, caches are scoped to the user running the build. These user-scoped caches allow for isolation so that modifications to the build and its cache are not unintentionally shared during development or between users.

Shared user behavior

When a single user-actor is shared between multiple people (such as when using access tokens or triggering builds from GitHub Actions), user-scoped cache rules still apply. This means that builds operating under that shared account will no longer have isolated caches and run the risk of sharing unintended artifacts. We recommend avoiding this by having designated jobs to only save clean caches, while the development builds only restore.

Designated build for cache creation

You can configure specific jobs to publish clean, new caches by disabling cache restoration and only saving the cache by leveraging the provided environment variables. These examples go over a few approaches to do so.

Disable restoring cache for production build profile

{
  "build": {
    "production": {
      "env": {
        "EAS_RESTORE_CACHE": "0",
        "EAS_SAVE_CACHE": "1"
      }
    },
    "preview": {
      "env": {
        "EAS_USE_CACHE": "1"
      }
    }
  }
}

Only save cache from designated jobs

To ensure only trusted sources publish cache, you can configure workflows to only save cache from specific jobs on the main branch:

jobs:
  build_production:
    type: build
    if: ${{ github.ref_name == 'main' }}
    env:
      EAS_RESTORE_CACHE: '0'
      EAS_SAVE_CACHE: '1'
    params:
      platform: android
      profile: production

Learn more about the usage and details of build cache in the documentation.

Feedback

We'd love to hear how build caches work for you. If you experience issues, please send an email to workflows@expo.io.

DEC 15, 2025

Workflows insights aggregates data from all of your workflow runs in the insights tab on the Expo dashboard. You can monitor key metrics like success rates, active runs, and failure counts, while observing trends over time to understand how your workflows are performing.

Understanding your workflows

Workflows insights collects and visualizes data about how your workflows are performing. This includes:

  • Success and failure rates of every workflow
  • Number of active workflows
  • Failed run counts
  • Trends over time

The graphs on the dashboard show runs over time and their statuses. This view allows you to spot patterns over time, like if a change introduced a regression or a bug fix solved a job's error. Below the graph, there's a table, which you can filter and search. It shows data about specific workflows and their runs, so that you can zero-in on specific workflows.

When to use workflows insights

Workflows insights are valuable when you have a team all using shared CI/CD workflows to build, test, and release your app. For example, if you have end-to-end tests as a part of your release process, this dashboard can help you easily identify when a test job started erroring, which is a vital clue when debugging which change introduced an issue.

This dashboard is also helpful when you want to analyze CI/CD usage across your organization. You might spot that a vital workflow is taking longer than you'd like, which could lead to an effort to optimize that workflow to run faster. Workflows insights turns anecdotal data into quantitative data that helps you and your team make decisions.

Getting started

Workflows insights is available to organizations on the Production and Enterprise plans. You can access insights by going to your project's insights tab.

Once there, you can customize the timeframe from 1 hour to 90 days of data. You can also export your data directly if you'd like to find insights of your own.

Workflows insights makes it easier to maintain reliable workflows at scale, giving you the visibility you need to keep your CI/CD pipelines healthy.

December 10, 2025 | By Stanisław Chmiela

Overview

The Slack job sends a message during a workflow run to your Slack workspace. Since this is an API call to Slack's servers, the Slack job is configured to skip spinning up a VM and instead send the API call immediately when this job runs. This means your Slack messages will be sent nearly instantaneously.

Using the Slack job

To send a message to a Slack channel, add a job with type: slack to your workflow:

name: Send message

jobs:
  send_slack_message:
    name: Send Slack message
    type: slack
    params:
      webhook_url: {{ env.SLACK_WEBHOOK_URL }}
      message: "Hello Slack! 🎉"

The webhook URL can be obtained from your Slack workspace's app settings.

Examples

Conditional build failure notification

You can send a message to a channel conditionally when a build fails:

name: Build Notification

jobs:
  build_ios:
    name: Build iOS
    type: build
    params:
      platform: ios
      profile: production

  notify_build:
    name: Notify Build Status
    needs: [build_ios]
    type: slack
    params:
      webhook_url: {{ env.SLACK_WEBHOOK_URL }}
      message: 'Build completed for app ${{ after.build_ios.outputs.app_identifier }} (version ${{ after.build_ios.outputs.app_version }})'
Rich Slack messages

You can also compose rich Slack messages. This is great for if your team coordinates testing or releases from a shared Slack channel that gets various types of messages:

name: Rich Build Notification

jobs:
  build_android:
    name: Build Android
    type: build
    params:
      platform: android
      profile: production

  notify_build:
    name: Notify Build Status
    needs: [build_android]
    type: slack
    params:
      webhook_url: {{ env.SLACK_WEBHOOK_URL }}
      payload:
        blocks:
          - type: header
            text:
              type: plain_text
              text: 'Build Completed'
          - type: section
            fields:
              - type: mrkdwn
                text: "*App:*\n${{ needs.build_android.outputs.app_identifier }}"
              - type: mrkdwn
                text: "*Version:*\n${{ needs.build_android.outputs.app_version }}"
          - type: section
            fields:
              - type: mrkdwn
                text: "*Build ID:*\n${{ needs.build_android.outputs.build_id }}"
              - type: mrkdwn
                text: "*Platform:*\n${{ needs.build_android.outputs.platform }}"
          - type: section
            text:
              type: mrkdwn
              text: 'Distribution: ${{ needs.build_android.outputs.distribution }}'

Learn more about the Slack job type in the pre-packaged jobs documentation.

Date: DEC 5, 2025
Authors: Phil Pluckthun, Vojtech Novak

Here's what you need to know when it comes to if your Expo app is affected by recent security vulnerabilities in React Server Components and how to upgrade it to stay protected.

Updates

UPDATED: on January 26, new DoS mitigations were published. The mitigation instructions are the same as for the previous security patches, and we have again restricted our peer dependency ranges in:

  • expo-router@55.0.0-preview.5 and jest-expo@55.0.6 for SDK55
  • expo-router@6.0.23 and jest-expo@54.0.17 for SDK 54
  • expo-router@5.1.11 and jest-expo@53.0.14 for SDK 53

UPDATED December 11 and December 12, 2025: Three new vulnerabilities (CVE-2025-55184, CVE-2025-55183, and CVE-2025-67779) were disclosed on December 11 affecting React Server Components. We have released additional patches to address these issues:

  • expo-router@6.0.19 and jest-expo@54.0.16 for SDK 54
  • expo-router@5.1.10 and jest-expo@53.0.13 for SDK 53
  • expo-router@7.0.0-canary-20251211-7da85ea and jest-expo@55.0.0-canary-20251211-7da85ea for canary

Learn more in the react.dev blogpost.

Follow the updated mitigation steps below, which now include newer react-server-dom-webpack versions to address the total of four vulnerabilities.

Previously, on December 3, an unauthenticated remote code execution vulnerability in React Server Components was disclosed as CVE-2025-55182. You may learn more about the vulnerability in this react.dev blogpost.

Am I affected?

First, you only need to take action if you're using experimental RSC or Server Functions support. If you use Expo only for client-side Android, iOS, and web, then you are not affected. API routes are not affected.

Expo projects can be vulnerable through a dependency on react-server-dom-webpack 19.0.0, 19.0.1, 19.1.0, 19.1.1, 19.1.2, 19.2.0, and 19.2.1. Projects that do not use RSC typically won't even have a dependency on the vulnerable package.

What to do (if affected)

To mitigate the vulnerabilities in your project's dependencies, you need to use a version of react-server-dom-webpack according to the list below:

  • react-server-dom-webpack@19.1.4 for SDK 54 (with react 19.1.x)
  • react-server-dom-webpack@19.0.3 for SDK 53 (with react 19.0.x)
  • react-server-dom-webpack@19.2.3 for canary (with react 19.2.x)

You can install the appropriate version manually to mitigate the issue. If you're using npm: Due to peer dependencies errors, you may have to add react-server-dom-webpack to your package.json:overrides.

We have published patches for Expo SDK 53, 54 and canary that restrict our peer dependency ranges to only allow the patched react-server-dom-webpack versions mentioned above. Earlier versions of Expo are not affected.

These patches were published as listed below:

Additionally, we have updated our version recommendations in the expo CLI. Running expo install --check will recommend updates to react-server-dom-webpack, if you directly depend on an affected version. The React team has deprecated the affected versions, which means your package manager should additionally flag these versions, if they're still installed in your project.

Verify the upgrade

After upgrading, verify that your project depends on the expected version of react-server-dom-webpack using npm explain react-server-dom-webpack / yarn why react-server-dom-webpack and similar, depending on your package manager.

React & React Native Version Compatibility

Keep in mind that specific versions of react-native require specific versions of react, to prevent a runtime version mismatch. Always follow the react (and react-dom) version recommendations from expo install --check to prevent mismatches.

React versions in a monorepo

If you're using a monorepo and must update react, SDK 54 allows you to keep your Expo app on the correct react version by enabling expo.experiments.autolinkingModuleResolution: true in your app.json. This helps if other web apps or projects must be updated to a newer version of react while keeping your React Native / Expo app on an older version.

The react package itself does not contain RSC functionality and is hence not affected by these vulnerabilities.

Summary

  • Only experimental RSC and Server Functions are affected. Apps that use Expo only for client-side Android, iOS, and web are not affected.
  • You can update just the affected dependency manually or install the releases of expo-router and jest-expo according to the table above.
  • Specific versions of react-native require specific versions of react.
  • In a monorepo, you may use a different version of react for an Expo app and other web projects.

November 24, 2025

By Byron Karlen

Organizations on Production and Enterprise plans can now require members to enable two-factor authentication (2FA). For organizations with SSO enabled, this requirement only applies to non-SSO users.

Why require 2FA?

2FA adds an essential layer of security to your organization by ensuring that compromised passwords alone cannot grant unauthorized access to your projects, credentials, and builds. By requiring 2FA at the organization level, you enforce consistent security standards across your team.

How it works

  • New members can accept an invitation to your organization only once they have enabled 2FA
  • Members are prevented from disabling 2FA once enabled
  • When 2FA becomes required, existing members without 2FA remain in the organization. They should enable it themselves, or be removed and re-invited to enforce the requirement.

Organization owners and admins can enable this requirement from the account's settings page.

November 21, 2025

By default, all projects have three environment variable environments: development, preview, and production. This setup works well for most hobby and small projects, but we've heard from larger teams and those with more complex workflows that they need additional flexibility. We're excited to announce that we've just shipped the ability to configure custom environments! This feature is available on all Production and Enterprise plans.

How it works

When creating an environment variable, click on the + icon next to Environments, and enter a custom environment name for the variable. Once this variable has been created, subsequent variables will have the new environment - in this case test - listed as available environment.

To delete a custom environment, delete all environment variables assigned to it.

With the eas-cli

Custom environments support was added in eas-cli@16.23.0. In practice this means that the --environment flag now accepts any string as opposed to only the default development, preview and production.

Now you may pass in test as the environment field in eas.json or workflows files which will ensure the build will pull variables from the new environment.

Limits

  • An environment name can contain letters, digits, underscores and hyphens, and be between 3-100 characters
  • Project environment variables are limited to 200 per environment
  • App-wide environment variables are limited to 150 per environment
  • Custom environments are limited to 10 per project

If you have a use-case that exceeds these limits, please get in touch!

Date: October 14, 2025

We're deprecating the GitHub build triggers feature to focus our efforts on delivering the best mobile CI/CD experience through EAS Workflows.

What this means:

  • Existing projects with GitHub build triggers configured will continue to work
  • New projects will no longer have access to this feature

Why EAS Workflows?

EAS Workflows gives you everything GitHub build triggers offered and much more. You can still trigger builds automatically from GitHub, but now with the flexibility and power to automate your entire mobile development workflow—from running tests and checks to custom deployment pipelines and beyond.

Get started: Learn more about EAS Workflows

Published: SEP 30, 2025

Scheduling workflows allows you to automate routine tasks like building and submitting your app on a regular cadence. This eliminates the need to manually trigger builds and submissions, so that you can do things like have your biweekly production builds already submitted to the app stores by the time you arrive at work.

How to schedule workflows

Use the on.schedule.cron trigger in your workflow configuration with unix-cron syntax:

name: Example cron job trigger workflow

on:
  push:
    branches: ["main"]
  schedule:
    - cron: "0 9 1-7,15-21 * 1" # Runs every two weeks on Monday at 9 AM GMT

jobs:
  # ... Your workflow jobs here

See the workflow examples for pre-made configurations for core app development use cases.

Scheduling considerations

Scheduled workflows have several important characteristics to keep in mind:

  • Default branch only: Scheduled workflows run exclusively on the default branch of your repository, typically main
  • GMT timezone: All scheduled workflows execute in Greenwich Mean Time (GMT)
  • Multiple schedules: A single workflow can have multiple cron schedules for different timing requirements
  • High load delays: During peak usage times, particularly at the start of each hour, scheduled workflows may experience delays
  • Idempotent design: In rare cases, jobs may be skipped or run multiple times, so make sure your workflows handle repeated execution gracefully

Common scheduling patterns

You can use crontab guru to generate and validate cron expressions. Here are some useful patterns:

  • 0 0 * * * - Daily at midnight GMT
  • 0 9 * * 1 - Every Monday at 9 AM GMT
  • 0 0 1,15 * * - Twice monthly on the 1st and 15th at midnight GMT
  • 0 6 * * 1-5 - Weekdays at 6 AM GMT

Wrap up

Scheduling your deployment workflows means your builds and submissions can be processed automatically during off hours. When you arrive at work, your latest app version is already built and submitted, ready for you to release through the app store consoles. Schedules remove some of the toil around repeated tasks, like releasing to the app stores, or running nightly end-to-end tests.

Learn more about the scheduled job syntax in the documentation.

Last Checked
5h ago
Latest
May 26, 2026
Tracking since Dec 19, 2023