<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dan</title>
    <description>The latest articles on DEV Community by Dan (@pestodrizzle).</description>
    <link>https://dev.to/pestodrizzle</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1258232%2F55299958-a5af-42c5-9e73-34fa1cdfde6c.jpeg</url>
      <title>DEV Community: Dan</title>
      <link>https://dev.to/pestodrizzle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pestodrizzle"/>
    <language>en</language>
    <item>
      <title>Expo Router v56 Ships SSR and Breaks Free from React Navigation</title>
      <dc:creator>Dan</dc:creator>
      <pubDate>Wed, 10 Jun 2026 13:15:36 +0000</pubDate>
      <link>https://dev.to/expo/expo-router-v56-ships-ssr-and-breaks-free-from-react-navigation-4pfb</link>
      <guid>https://dev.to/expo/expo-router-v56-ships-ssr-and-breaks-free-from-react-navigation-4pfb</guid>
      <description>&lt;p&gt;Expo Router just made its biggest change since launch. Version 56 forks from React Navigation, adds streaming server-side rendering, and gives Android developers the toolbar API they've been waiting for.&lt;/p&gt;

&lt;p&gt;The fork is the big story here, affecting every Expo Router app. But there's more: new SSR capabilities, an Android toolbar that matches iOS, and expanded Native Tabs. Let's break down what changed and why it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The React Navigation fork explained
&lt;/h2&gt;

&lt;p&gt;Expo has deep roots with React Navigation. &lt;a href="https://x.com/notbrent?s=20" rel="noopener noreferrer"&gt;Brent Vatne&lt;/a&gt;, Expo's VP of Engineering, led React Navigation through versions 1.0 and 2.0 alongside &lt;a href="https://github.com/ericvicenti" rel="noopener noreferrer"&gt;@ericvicenti&lt;/a&gt; and &lt;a href="https://github.com/satya164" rel="noopener noreferrer"&gt;@satya164&lt;/a&gt;. We helped React Navigation grow from an early experiment, influenced by React Native's &lt;code&gt;NavigationExperimental&lt;/code&gt; and our own &lt;code&gt;ex-navigation&lt;/code&gt;, into the standard navigation solution for React Native.&lt;/p&gt;

&lt;p&gt;React Navigation remains in Satya's capable hands while our focus shifted to Expo Router. As Expo Router evolved, we needed deeper control over React Navigation's internals. After talking with Satya, we agreed that forking the pieces Expo Router depends on would serve both projects better.&lt;/p&gt;

&lt;p&gt;This isn't about abandoning React Navigation. It's about giving each project room to grow in different directions.&lt;/p&gt;

&lt;p&gt;For Expo Router, the fork opens space for changes specific to file-based routing, web features, and server rendering. Some improvements we want to make wouldn't help React Navigation users directly, and pushing those changes upstream would add unnecessary complexity.&lt;/p&gt;

&lt;p&gt;We can now create a focused version of React Navigation's internals that works specifically for Expo Router. This lets us simplify integration, reduce workaround code, and make maintenance easier.&lt;/p&gt;

&lt;p&gt;There's also a practical benefit: dependency versioning. Projects sometimes ended up with multiple React Navigation versions installed, causing conflicts. Forking the internal pieces gives us better control over compatibility and makes releases more predictable.&lt;/p&gt;

&lt;p&gt;Expo Router's web capabilities like data loaders and server-side rendering need changes that are easier to make in a codebase built specifically for these features.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the fork affects your code
&lt;/h2&gt;

&lt;p&gt;Since Expo Router no longer depends on React Navigation, you can't import code directly from &lt;code&gt;@react-navigation/*&lt;/code&gt; packages. We know this breaks existing code, so we built tools to help with migration.&lt;/p&gt;

&lt;p&gt;Use our codemod to migrate all React Navigation imports to &lt;code&gt;expo-router/react-navigation&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;Our &lt;a href="https://docs.expo.dev/router/migrate/sdk-55-to-56/" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt; walks through manual migration steps. To ease the transition, imports of &lt;code&gt;@react-navigation/core&lt;/code&gt; from libraries will automatically redirect to &lt;code&gt;expo-router/react-navigation&lt;/code&gt; for at least one release cycle.&lt;/p&gt;

&lt;p&gt;If you run into problems, let us know.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next for both projects
&lt;/h2&gt;

&lt;p&gt;We're working with the React Navigation team on a shared API for library authors who need to support both libraries.&lt;/p&gt;

&lt;p&gt;If you maintain a library that should work with both Expo Router and React Navigation, reach out. We'd like to collaborate on adapting it to the new APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Suspense fallbacks
&lt;/h2&gt;

&lt;p&gt;You can now customize the Suspense fallback for routes within a &lt;code&gt;_layout&lt;/code&gt;. &lt;a href="https://docs.expo.dev/router/error-handling/#loading-states-with-suspense-fallback" rel="noopener noreferrer"&gt;Export a &lt;/a&gt;&lt;code&gt;[SuspenseFallback](https://docs.expo.dev/router/error-handling/#loading-states-with-suspense-fallback)&lt;/code&gt;&lt;a href="https://docs.expo.dev/router/error-handling/#loading-states-with-suspense-fallback" rel="noopener noreferrer"&gt; to customize the loading screen&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ActivityIndicator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;View&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expo-router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SuspenseFallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;View&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;alignItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ActivityIndicator&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;large&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/View&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Streaming SSR arrives
&lt;/h2&gt;

&lt;p&gt;Expo Router already supported &lt;a href="https://docs.expo.dev/router/web/api-routes/" rel="noopener noreferrer"&gt;API routes&lt;/a&gt;, &lt;a href="https://docs.expo.dev/router/web/middleware/" rel="noopener noreferrer"&gt;middleware&lt;/a&gt;, &lt;a href="http://docs.expo.dev/router/web/data-loaders/" rel="noopener noreferrer"&gt;data loaders&lt;/a&gt;, and &lt;a href="https://docs.expo.dev/router/web/static-rendering/" rel="noopener noreferrer"&gt;static rendering&lt;/a&gt;. Version 56 adds streaming &lt;a href="https://docs.expo.dev/router/web/server-rendering/" rel="noopener noreferrer"&gt;server-side rendering&lt;/a&gt;, which improves perceived performance by letting you prioritize important UI elements over slower, data-dependent components.&lt;/p&gt;

&lt;p&gt;Enable it by setting &lt;code&gt;unstable_useServerRendering&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; in your Expo Router config.&lt;/p&gt;

&lt;p&gt;Streaming SSR includes a new way to generate HTML metadata using a &lt;a href="https://docs.expo.dev/router/web/server-rendering/#metadata" rel="noopener noreferrer"&gt;generateMetadata&lt;/a&gt; function.&lt;/p&gt;

&lt;p&gt;We also added helper utilities for type-safe data loaders: &lt;code&gt;[createStaticLoader](https://docs.expo.dev/versions/latest/sdk/server/#createstaticloaderfn)&lt;/code&gt; and &lt;code&gt;[createServerLoader](https://docs.expo.dev/versions/latest/sdk/server/#createserverloaderfn)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android gets the toolbar
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://expo.dev/changelog/sdk-55" rel="noopener noreferrer"&gt;SDK 55&lt;/a&gt; brought toolbar support to iOS. Version 56 adds the same feature to Android, achieving cross-platform compatibility. Like iOS, you can place the Android toolbar in three positions: left, right, and bottom.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqi8wa3s60ey4ezrzeumw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqi8wa3s60ey4ezrzeumw.png" alt="Android toolbar in Expo Router" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See all available options in the toolbar &lt;a href="https://docs.expo.dev/router/advanced/stack-toolbar/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Native Tabs expansion
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Last fall, &lt;a href="https://expo.dev/blog/expo-router-v6" rel="noopener noreferrer"&gt;in Router v6&lt;/a&gt; (&lt;em&gt;we changed our Router naming convention since then&lt;/em&gt;), we added Native Tabs support. We're working toward full cross-platform support and plan to mark the API as stable in the next release.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Expo Router v56 adds new options to Native Tabs. The most requested feature was preventing tab selection. You can now use the &lt;code&gt;disabled&lt;/code&gt; property to show a tab in the tab bar while preventing user interaction.&lt;/p&gt;

&lt;p&gt;Check all available options and platform support in the Native Tabs &lt;a href="https://docs.expo.dev/versions/latest/sdk/router/native-tabs/" rel="noopener noreferrer"&gt;reference page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started with v56
&lt;/h2&gt;

&lt;p&gt;Three key takeaways from this release:&lt;/p&gt;

&lt;p&gt;The React Navigation fork requires a one-time import update for every Expo Router app. Our codemod and compatibility layer make migration straightforward.&lt;/p&gt;

&lt;p&gt;Streaming SSR, &lt;code&gt;generateMetadata&lt;/code&gt;, and new loader helpers improve web experiences with Expo Router.&lt;/p&gt;

&lt;p&gt;Android now has toolbar support and expanded Native Tabs, matching iOS feature parity.&lt;/p&gt;

&lt;p&gt;For existing apps, start with the &lt;a href="https://docs.expo.dev/router/migrate/sdk-55-to-56/" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt; and run the codemod. New projects get all these features by default in SDK 56.&lt;/p&gt;

&lt;p&gt;Find us on &lt;a href="https://chat.expo.dev/" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; and &lt;a href="https://github.com/expo/expo" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. We want to hear what you build and when something breaks.&lt;/p&gt;

&lt;p&gt;This post is based on content from the &lt;a href="https://expo.dev/blog/expo-router-v56-decoupling-from-react-navigation" rel="noopener noreferrer"&gt;Expo blog&lt;/a&gt;. Follow &lt;a href="https://dev.to/expo"&gt;@expo&lt;/a&gt; for more React Native content.&lt;/p&gt;

</description>
      <category>expo</category>
      <category>mobile</category>
      <category>reactnative</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Write Swift and Kotlin directly in your React Native app with Expo SDK 56</title>
      <dc:creator>Dan</dc:creator>
      <pubDate>Tue, 09 Jun 2026 13:19:36 +0000</pubDate>
      <link>https://dev.to/expo/write-swift-and-kotlin-directly-in-your-react-native-app-with-expo-sdk-56-anh</link>
      <guid>https://dev.to/expo/write-swift-and-kotlin-directly-in-your-react-native-app-with-expo-sdk-56-anh</guid>
      <description>&lt;p&gt;Working with native code in React Native usually involves creating separate packages and manually maintaining TypeScript interfaces. Expo SDK 56 changes this by letting you write Swift and Kotlin files right alongside your React components and automatically generating the TypeScript types for you.&lt;/p&gt;

&lt;p&gt;Writing Expo modules traditionally meant dealing with package boilerplate and keeping multiple interfaces in sync. You'd create a module as a standalone package, then manually maintain TypeScript interfaces that matched your native Swift and Kotlin code. This workflow worked but added friction.&lt;/p&gt;

&lt;p&gt;SDK 56 introduces &lt;strong&gt;inline modules&lt;/strong&gt; and the &lt;code&gt;expo-type-information&lt;/code&gt; package to remove these pain points. You can now write native modules directly in your project structure and generate matching TypeScript types automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing native code next to your components
&lt;/h2&gt;

&lt;p&gt;Inline modules let you place Swift and Kotlin files anywhere in your project structure. Need a custom native view? Create &lt;code&gt;NativeView.kt&lt;/code&gt; and &lt;code&gt;NativeView.swift&lt;/code&gt; files right next to your &lt;code&gt;App.tsx&lt;/code&gt; and write your view there.&lt;/p&gt;

&lt;p&gt;Setting up inline modules is simple. In your app configuration file, specify &lt;code&gt;watchedDirectories&lt;/code&gt; - the directories containing your inline modules. After running &lt;code&gt;npx expo prebuild&lt;/code&gt; to sync the native projects, you're ready to go.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"experiments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"inlineModules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"watchedDirectories"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;"app"&lt;/code&gt; in your &lt;code&gt;watchedDirectories&lt;/code&gt;, you can create Swift and Kotlin files anywhere within the &lt;code&gt;app&lt;/code&gt; directory or its subdirectories. Open &lt;code&gt;app/nested/InlineModule.swift&lt;/code&gt; and write your Expo module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;internal&lt;/span&gt; &lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;ExpoModulesCore&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;InlineModule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Module&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;ModuleDefinition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Constant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello iOS inline modules!"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once written, access it from JavaScript using &lt;code&gt;requireNativeModule('InlineModule')&lt;/code&gt;. For views, use &lt;code&gt;requireNativeView('InlineModule')&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatic TypeScript type generation
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;expo-type-information&lt;/code&gt; package parses your Swift modules and generates matching TypeScript types automatically. This eliminates the manual work of maintaining type interfaces.&lt;/p&gt;

&lt;p&gt;The package includes a CLI tool with commands designed for inline modules.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpsg14dkyzuke1grvt2ee.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpsg14dkyzuke1grvt2ee.png" alt="CLI Tool" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;inline-modules-interface&lt;/code&gt; command finds all Swift inline modules in your project and generates two TypeScript files for each one. After running the command, you'll see these files appear next to your Swift file:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkt54wkytu34vnq0l9f6r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkt54wkytu34vnq0l9f6r.png" alt="InlineModule" width="492" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Generated File&lt;/strong&gt; (&lt;code&gt;[ModuleName].generated.ts&lt;/code&gt;) contains all type information about the module, including function declarations, constants, classes, and views. This file gets overwritten every time you run the command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// InlineModule.generated.ts&lt;/span&gt;
&lt;span class="cm"&gt;/*Automatically generated by expo-type-information.*/&lt;/span&gt;

&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;ViewProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;react&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;native&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;NativeModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;expo&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;InlineModuleNativeModuleType&lt;/span&gt; &lt;span class="n"&gt;extends&lt;/span&gt; &lt;span class="kt"&gt;NativeModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;Stable File&lt;/strong&gt; (&lt;code&gt;[ModuleName].tsx&lt;/code&gt;) re-exports the module interface and provides a default export for the main view if one exists. You can edit this file and it won't be overwritten.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// InlineModule.tsx&lt;/span&gt;
&lt;span class="c1"&gt;// File hash: 8dfc86f5416afbe08cc1ee581c850fc9cec446479211d85501d9a5e2d24cc534&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;InlineModuleNativeModuleType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="kt"&gt;InlineModule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generated&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;requireNativeModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requireNativeView&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;expo&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;InlineModule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;InlineModuleNativeModuleType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;requireNativeModule&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;InlineModuleNativeModuleType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="kt"&gt;InlineModule&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;InlineModule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This split lets you tweak imperfect generated output while still allowing core declarations to be regenerated when you update the native side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current limitations
&lt;/h2&gt;

&lt;p&gt;File naming requires that an inline module's name exactly matches its file name. Module names must be globally unique since the name identifies the module in the global object. You can't have both &lt;code&gt;app/InlineView.swift&lt;/code&gt; and &lt;code&gt;src/InlineView.swift&lt;/code&gt; in the same project.&lt;/p&gt;

&lt;p&gt;Type generation currently only works for Swift modules and on macOS.&lt;/p&gt;

&lt;h3&gt;
  
  
  When types can't be resolved
&lt;/h3&gt;

&lt;p&gt;Sometimes the tool can't resolve types of native module declarations. This happens because of &lt;code&gt;SourceKitten&lt;/code&gt; limitations and our decision to parse only provided files instead of doing full compilation. When we can't resolve a Swift type, we generate an &lt;code&gt;unknown&lt;/code&gt; type in TypeScript.&lt;/p&gt;

&lt;p&gt;Common scenarios include nested declarations where &lt;code&gt;SourceKitten&lt;/code&gt; has trouble with deeply nested closures, making it hard to find types inside a &lt;code&gt;Class&lt;/code&gt;. Consider this &lt;code&gt;ExpoBlob&lt;/code&gt; module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ExpoBlob.swift&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Foundation&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;ExpoModulesCore&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ExpoBlob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Module&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;ModuleDefinition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ExpoBlob"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kt"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;Constructor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;blobParts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;EitherOfThree&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;TypedArray&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]?,&lt;/span&gt; &lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;BlobOptions&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;endings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endings&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transparent&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;blobPartsProcessed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;processBlobParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blobParts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;endings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;endings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;blobParts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blobPartsProcessed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="kt"&gt;BlobOptions&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kt"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kt"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kt"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"slice"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="nv"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="nv"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;blobSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;safeStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;safeEnd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;blobSize&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;relativeStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;safeStart&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blobSize&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;safeStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safeStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blobSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;relativeEnd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;safeEnd&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blobSize&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;safeEnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safeEnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blobSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;relativeStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;relativeEnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contentType&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kt"&gt;AsyncFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kt"&gt;AsyncFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bytes"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ExpoBlob.generated.ts&lt;/span&gt;
&lt;span class="cm"&gt;/*Automatically generated by expo-type-information.*/&lt;/span&gt;

&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;ViewProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;react&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;native&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;NativeModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;expo&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// These types haven't been defined in provided file(s).&lt;/span&gt;
&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;TypedArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;BlobOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;endings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;EndingType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;EndingType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;transparent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;transparent&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;native&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;native&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;BlobPart&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Blob&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;undefined&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;unknown&lt;/span&gt; &lt;span class="cm"&gt;/*The type couldn't be resolved automatically.*/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;readonly&lt;/span&gt; &lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;unknown&lt;/span&gt; &lt;span class="cm"&gt;/*The type couldn't be resolved automatically.*/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;readonly&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;unknown&lt;/span&gt; &lt;span class="cm"&gt;/*The type couldn't be resolved automatically.*/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;blobParts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;Blob&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;TypedArray&lt;/span&gt;&lt;span class="p"&gt;)[]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;BlobOptions&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;undefined&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ExpoBlobNativeModuleType&lt;/span&gt; &lt;span class="n"&gt;extends&lt;/span&gt; &lt;span class="kt"&gt;NativeModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;typeof&lt;/span&gt; &lt;span class="kt"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how the return types of &lt;code&gt;slice&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, and &lt;code&gt;size&lt;/code&gt; haven't been resolved. You can often fix this by manually annotating the return type of the closure in Swift.&lt;/p&gt;

&lt;p&gt;Imported declarations also cause issues since we only parse provided files and ignore imports. If a function or type from outside influences a return value, the tool may not resolve it.&lt;/p&gt;

&lt;p&gt;The tool struggles with return types if you omit the &lt;code&gt;return&lt;/code&gt; keyword and don't explicitly annotate the closure. To avoid this, annotate DSL declarations or include the &lt;code&gt;return&lt;/code&gt; keyword. Try using &lt;code&gt;--type-inference PREPROCESS_AND_INFERENCE&lt;/code&gt; in the CLI for better return type inference.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works under the hood
&lt;/h2&gt;

&lt;p&gt;Let's examine what happens when you use inline modules. Suppose you've created a module at &lt;code&gt;app/nested/InlineModule.swift&lt;/code&gt; and set &lt;code&gt;watchedDirectories&lt;/code&gt; to include the &lt;code&gt;app&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;experiments&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inlineModules&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;watchedDirectories&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Prebuild process
&lt;/h3&gt;

&lt;p&gt;Running &lt;code&gt;npx expo prebuild&lt;/code&gt; updates your native projects in two ways:&lt;/p&gt;

&lt;p&gt;The Xcode project gets updated so the &lt;code&gt;app&lt;/code&gt; folder becomes a &lt;strong&gt;file system synchronized group&lt;/strong&gt;. This makes all files in the folder show up in the Xcode editor and get automatically included in the iOS build.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4tk405n9ze6gpqo17nqf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4tk405n9ze6gpqo17nqf.png" alt="file system synchronized group" width="799" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Project properties for both iOS and Android get updated with your &lt;code&gt;watchedDirectories&lt;/code&gt;. On Android, this is essential for adding files to Android Studio. These properties are used later during autolinking.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Podfile.properties.json&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s"&gt;"expo.jsEngine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hermes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"EX_DEV_CLIENT_NETWORK_INSPECTOR"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"expo.inlineModules.watchedDirectories"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;]"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// gradle.properties&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;expo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inlineModules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;watchedDirectories&lt;/span&gt;&lt;span class="p"&gt;=[&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Android project updates
&lt;/h3&gt;

&lt;p&gt;On Android, the project updates during the Gradle configuration phase. This happens when you click &lt;strong&gt;Sync Project with Gradle Files&lt;/strong&gt; in Android Studio or automatically before building the app.&lt;/p&gt;

&lt;p&gt;During this phase, a folder structure gets created that mirrors your &lt;code&gt;watchedDirectories&lt;/code&gt;. Symlinks are created to the Kotlin files inside those directory trees.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftqif0wg5wgydxx96wott.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftqif0wg5wgydxx96wott.png" alt="watchedDirectories" width="800" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This mirror structure ensures native files get compiled and are visible in Android Studio, while other project files are ignored. JavaScript and TypeScript files won't be indexed by Android Studio since they're not in this mirror directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Module registration
&lt;/h3&gt;

&lt;p&gt;All Expo modules live on a global object exposed through native &lt;strong&gt;module providers&lt;/strong&gt;. During build on both iOS and Android, a module provider class gets generated containing references to all regular Expo modules and your inline modules. When you call &lt;code&gt;requireNativeModule('InlineModule')&lt;/code&gt; in JavaScript, it accesses this global object.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type generation internals
&lt;/h2&gt;

&lt;p&gt;Type generation works by parsing the native module's structured declaration and generating its TypeScript interface directly from native code. The &lt;code&gt;expo-type-information&lt;/code&gt; package has four main parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Swift parser&lt;/li&gt;
&lt;li&gt;Abstraction over module types
&lt;/li&gt;
&lt;li&gt;TypeScript code generator&lt;/li&gt;
&lt;li&gt;CLI tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They work together to automate writing TypeScript interfaces for your modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bridging type systems
&lt;/h3&gt;

&lt;p&gt;When defining a module in the DSL, you provide the native interface with functions, constants, classes, and views - all strictly typed in Swift's type system.&lt;/p&gt;

&lt;p&gt;When interfacing with the module from TypeScript, you're working with JavaScript objects under TypeScript's type system. This differs from Swift or Kotlin, and there's no strict one-to-one mapping between them. The conversions between JavaScript and Swift aren't always obvious.&lt;/p&gt;

&lt;p&gt;Expo provides converters for many types, but multiple TypeScript constructs sometimes convert to the same Swift type.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frp8y7b5pit0mpj3207z1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frp8y7b5pit0mpj3207z1.png" alt="Type systems" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, when working with Swift's &lt;code&gt;UIColor&lt;/code&gt;, Expo can convert several JavaScript objects: color strings ('red'), hex strings (&lt;code&gt;#00ffaa00&lt;/code&gt;), or hex numbers (&lt;code&gt;0xff66dd00&lt;/code&gt;). All of these can be type-annotated differently in TypeScript - &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;number&lt;/code&gt; and &lt;code&gt;ColorValue&lt;/code&gt; (from &lt;code&gt;react-native&lt;/code&gt;) all convert to &lt;code&gt;UIColor&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Currently we only support mapping basic types (&lt;code&gt;number&lt;/code&gt;, &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;boolean&lt;/code&gt;, etc.). Check the exact list in the &lt;a href="https://docs.expo.dev/modules/type-generation-reference/" rel="noopener noreferrer"&gt;reference&lt;/a&gt;. We'll continue updating the package with additional type mappings based on converters available in &lt;code&gt;expo-modules-core&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Library components
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;expo-type-information&lt;/code&gt; package consists of several components working together.&lt;/p&gt;

&lt;h4&gt;
  
  
  Swift file parsing
&lt;/h4&gt;

&lt;p&gt;We use &lt;code&gt;SourceKitten&lt;/code&gt; to parse Swift files. &lt;code&gt;SourceKitten&lt;/code&gt; provides structured information about the whole code, letting us parse the Swift DSL, enums, and structs.&lt;/p&gt;

&lt;p&gt;Consider the &lt;code&gt;Hello&lt;/code&gt; constant declaration from &lt;strong&gt;InlineModule.swift&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;Constant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello iOS inline modules!"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That Swift declaration corresponds to this &lt;code&gt;SourceKitten&lt;/code&gt; output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key.bodylength"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key.bodyoffset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;124&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key.kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"source.lang.swift.expr.call"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key.length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;66&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key.name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Constant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key.namelength"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key.nameoffset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;115&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key.offset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;115&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key.substructure"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key.bodylength"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key.bodyoffset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;124&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key.kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"source.lang.swift.expr.argument"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key.length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key.offset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;124&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key.bodylength"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key.bodyoffset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;133&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key.kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"source.lang.swift.expr.argument"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key.length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key.offset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;133&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key.substructure"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"key.bodylength"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;46&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"key.bodyoffset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;134&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"key.kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"source.lang.swift.expr.closure"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"key.length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"key.offset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;133&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"key.substructure"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"key.bodylength"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;46&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"key.bodyoffset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;134&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"key.kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"source.lang.swift.stmt.brace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"key.length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"key.offset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;133&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SourceKitten&lt;/code&gt; lets us parse just a single file. This saves time since it doesn't have to compile the whole Xcode project, which is essential when you want to constantly regenerate TypeScript interfaces. But not having access to the whole project means types and functions defined in other files can't be resolved.&lt;/p&gt;

&lt;h4&gt;
  
  
  Type information abstraction
&lt;/h4&gt;

&lt;p&gt;An abstraction layer defines what type information is relevant for Expo modules. The abstraction is agnostic to the underlying native language, so we can add Kotlin support in the future. It's close to the TypeScript type system since it generates TS declarations. Our &lt;code&gt;SourceKitten&lt;/code&gt;-based parser outputs this abstraction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * `FileTypeInformation` object abstracts over type related information in a file.
 * The abstraction is closely related to Typescript and expo NativeModules (both to be independent of the actual native side
 * and to give accurate information about what and how we can use the given module).
 * @header TypeInfoTypes
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FileTypeInformation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * @field Set of all type identifiers declared and used in the file.
   */&lt;/span&gt;
  &lt;span class="na"&gt;usedTypeIdentifiers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * @field Set of all type identifiers declared in the file.
   */&lt;/span&gt;
  &lt;span class="nl"&gt;declaredTypeIdentifiers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * @field For parametrized types it is the maximum number of parameters this type is used with.
   * This map is useful if we want to infer how many parameters a type declared in other file has.
   *
   * For example if `Set&amp;lt;string&amp;gt;` exists in a file then inferredTypeParametersCount['Set'] == 1.
   * If `Map&amp;lt;number, string&amp;gt;` exists then inferredTypeParametersCount['Map'] == 2.
   * If you use both `SomeParametrizedType&amp;lt;Type1, Type2&amp;gt;` and `SomeParametrizedType&amp;lt;Type3&amp;gt;` then inferredTypeParametersCount['SomeParametrizedType'] == 2.
   */&lt;/span&gt;
  &lt;span class="nl"&gt;inferredTypeParametersCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * @field Maps string identifier to the appropriate declaration object. For now only enum and records identifiers are mapped.
   */&lt;/span&gt;
  &lt;span class="nl"&gt;typeIdentifierDefinitionMap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TypeIdentifierDefinitionMap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * @field Array of all module classes declared in the given file.
   */&lt;/span&gt;
  &lt;span class="nl"&gt;moduleClasses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModuleClassDeclaration&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * @field Array of all record classes declared in the given file.
   */&lt;/span&gt;
  &lt;span class="nl"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RecordType&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * @field Array of all enums declared in the given file.
   */&lt;/span&gt;
  &lt;span class="nl"&gt;enums&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EnumType&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  TypeScript emission
&lt;/h4&gt;

&lt;p&gt;With Expo Modules types abstracted, we generate a TypeScript Abstract Syntax Tree (AST). We build this using the Compiler API with custom wrappers that handle different declaration types - imports, enums, classes, functions, types, interfaces. Once the AST is complete, we format the generated TypeScript with Prettier.&lt;/p&gt;

&lt;h4&gt;
  
  
  CLI interface
&lt;/h4&gt;

&lt;p&gt;The CLI tool sits on top of everything. It exposes commands to work with regular modules, inline modules, and to debug the functions from previous steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Check out the tutorials for &lt;a href="https://docs.expo.dev/modules/inline-modules-tutorial/" rel="noopener noreferrer"&gt;inline modules&lt;/a&gt; and &lt;a href="https://docs.expo.dev/modules/type-generation-tutorial/" rel="noopener noreferrer"&gt;type generation&lt;/a&gt;, plus the reference pages for &lt;a href="https://docs.expo.dev/modules/inline-modules-reference/" rel="noopener noreferrer"&gt;inline modules&lt;/a&gt; and the &lt;a href="https://docs.expo.dev/modules/type-generation-reference/" rel="noopener noreferrer"&gt;expo-type-information package&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Both features are experimental, so your feedback matters as we continue developing them. File an issue or create a pull request on GitHub, or share your thoughts on Twitter.&lt;/p&gt;

&lt;p&gt;This post is based on content from the &lt;a href="https://expo.dev/blog/native-code-expo-sdk-56" rel="noopener noreferrer"&gt;Expo blog&lt;/a&gt;. Follow &lt;a href="https://dev.to/expo"&gt;@expo&lt;/a&gt; for more React Native content.&lt;/p&gt;

</description>
      <category>expo</category>
      <category>mobile</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>Swift Calls JSI Directly in Expo SDK 56: Removing the Objective-C++ Layer</title>
      <dc:creator>Dan</dc:creator>
      <pubDate>Mon, 08 Jun 2026 20:28:57 +0000</pubDate>
      <link>https://dev.to/expo/swift-calls-jsi-directly-in-expo-sdk-56-removing-the-objective-c-layer-33jc</link>
      <guid>https://dev.to/expo/swift-calls-jsi-directly-in-expo-sdk-56-removing-the-objective-c-layer-33jc</guid>
      <description>&lt;p&gt;SDK 56 makes JavaScript to native calls significantly faster on iOS by letting Swift talk to JSI directly. We eliminated the Objective-C++ layer and saw 1.6-2.3x performance improvements across our benchmarks.&lt;/p&gt;

&lt;p&gt;Before this change, every native module call went through three languages. Now it's just Swift making a direct C++ call. Here's how we did it and what the performance gains look like.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three-language problem
&lt;/h2&gt;

&lt;p&gt;Prior to SDK 56, calling an &lt;a href="https://docs.expo.dev/modules/native-module-tutorial/" rel="noopener noreferrer"&gt;Expo native module&lt;/a&gt; from JavaScript meant crossing multiple language boundaries. Your Swift module code sat behind an Objective-C++ translation layer (&lt;code&gt;EXJavaScriptRuntime&lt;/code&gt;, &lt;code&gt;EXJavaScriptValue&lt;/code&gt;, etc.), which then called into JSI's C++ implementation.&lt;/p&gt;

&lt;p&gt;This architecture existed for one reason: Swift couldn't talk to C++ directly. Objective-C++ was the only practical bridge between them.&lt;/p&gt;

&lt;p&gt;The performance cost was significant. Every call crossed two language boundaries in each direction. Each value got converted twice: &lt;code&gt;std::string&lt;/code&gt; → &lt;code&gt;NSString&lt;/code&gt; → Swift &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;std::vector&lt;/code&gt; → &lt;code&gt;NSArray&lt;/code&gt; → Swift &lt;code&gt;Array&lt;/code&gt;. Each conversion allocated memory and copied data.&lt;/p&gt;

&lt;p&gt;Three different languages in the call path meant three different ways to debug problems. Stack traces changed shape mid-call. Memory management worked differently at each layer. When something went wrong, you had to understand all three languages to fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Swift/C++ interop changes the game
&lt;/h2&gt;

&lt;p&gt;Swift historically needed Objective-C as a bridge to reach C++. Any C++ type had to be wrapped in an Objective-C class before Swift could use it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.swift.org/documentation/cxx-interop/" rel="noopener noreferrer"&gt;Swift/C++ interop&lt;/a&gt; (introduced in Swift 5.9) removes this requirement. Swift can &lt;code&gt;import&lt;/code&gt; C++ headers directly. The compiler automatically maps C++ classes and methods onto Swift types you can use naturally.&lt;/p&gt;

&lt;p&gt;The result: what used to be a three-language relay race becomes a single Swift expression that compiles down to a direct C++ call. Performance matches what you'd get writing the call in C++ from the start.&lt;/p&gt;

&lt;p&gt;We're not the first to explore this in React Native. &lt;a href="https://nitro.margelo.com/" rel="noopener noreferrer"&gt;Nitro Modules&lt;/a&gt; pioneered this approach when Swift/C++ interop was even less mature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building ExpoModulesJSI
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ExpoModulesJSI&lt;/code&gt; is our Swift package that wraps JSI in Swift types. Despite the name, it's purely a JSI wrapper with no Expo-specific code. We could ship it standalone, but JSI only exists in React Native contexts, so the naming stays conservative.&lt;/p&gt;

&lt;p&gt;The type system mirrors JSI exactly: &lt;code&gt;JavaScriptRuntime&lt;/code&gt;, &lt;code&gt;JavaScriptValue&lt;/code&gt;, &lt;code&gt;JavaScriptObject&lt;/code&gt;, &lt;code&gt;JavaScriptArray&lt;/code&gt;, &lt;code&gt;JavaScriptFunction&lt;/code&gt;, etc. Each maps to its JSI equivalent but with a modern Swift API.&lt;/p&gt;

&lt;p&gt;We preserve JSI's ownership model using non-copyable types. JSI's value types like &lt;code&gt;jsi::Value&lt;/code&gt; and &lt;code&gt;jsi::Object&lt;/code&gt; own runtime resources and follow move-only semantics. Swift 5.9's &lt;code&gt;~Copyable&lt;/code&gt; protocol lets us mirror this behavior. The Swift compiler enforces the same single-owner rules that JSI expects underneath.&lt;/p&gt;

&lt;p&gt;The package builds as a SwiftPM package with C++ interop enabled, then gets bundled into an xcframework. Most React Native projects use CocoaPods, so we also provide a podspec that wraps the prebuilt binary. The podspec creates a stub xcframework at &lt;code&gt;pod install&lt;/code&gt; time, then a build script runs the real SwiftPM build with content-hash caching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling different concurrency models
&lt;/h2&gt;

&lt;p&gt;React Native's threading predates Swift Concurrency. JavaScript runs on a dedicated thread with a run loop. Native work uses &lt;code&gt;dispatch_queue_t&lt;/code&gt;s and callbacks. No actors, no &lt;code&gt;await&lt;/code&gt; points, just queues and blocks with thread-switching contracts.&lt;/p&gt;

&lt;p&gt;We wanted our Swift API to use modern Swift: &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;, structured concurrency, actor isolation where appropriate. This required building a bridge between Swift Concurrency and React Native's callback world without breaking either system's invariants.&lt;/p&gt;

&lt;p&gt;The boundary layer handles most of this work. We'll skip the implementation details here since they could fill another post and the design is still evolving under production load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation challenges
&lt;/h2&gt;

&lt;p&gt;Swift/C++ interop is experimental and comes with compilation costs. Here are the main issues we encountered:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Experimental status.&lt;/strong&gt; Years after Swift 5.9, C++ interop remains opt-in and officially experimental. APIs and behavior can change between Swift versions. Not a blocker for us, but worth knowing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capability gaps.&lt;/strong&gt; Swift and C++ have different memory models. ARC and value semantics versus manual lifetime management and raw pointers. Complex template metaprogramming and some inheritance patterns have no clean Swift mapping. Some gaps will close with tooling improvements; others are conceptually unbridgeable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compilation performance.&lt;/strong&gt; Enabling C++ interop adds noticeable compile time per file. It also spreads: any module importing an interop-enabled module must enable interop too. We solve this by shipping prebuilt xcframeworks. Apps link against binaries instead of recompiling interop sources, and downstream modules see a regular Swift library.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generated headers.&lt;/strong&gt; Swift emits a C++ header exposing all public symbols to C++. This gets large quickly and sometimes emits declarations in the wrong order. There's an undocumented flag &lt;code&gt;-clang-header-expose-decls=has-expose-attr&lt;/code&gt; that restricts the header to explicitly annotated declarations. It's mentioned only in &lt;code&gt;FrontendOptions.td&lt;/code&gt; in the Swift compiler source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third-party type annotations.&lt;/strong&gt; Swift imports C++ classes as value types by default, but types with virtual methods need reference semantics. For code we control, Swift provides macros like &lt;code&gt;SWIFT_SHARED_REFERENCE&lt;/code&gt;. For third-party headers like JSI, we use Clang's &lt;a href="https://clang.llvm.org/docs/APINotes.html" rel="noopener noreferrer"&gt;APINotes&lt;/a&gt; - YAML files that add import attributes without modifying the original headers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exception handling.&lt;/strong&gt; C++ exceptions don't cross into Swift. Swift assumes imported C++ functions don't throw unless proven otherwise. When JSI methods like &lt;code&gt;evaluateJavaScript&lt;/code&gt; throw &lt;code&gt;jsi::JSError&lt;/code&gt;, the exception crashes the app if it reaches Swift frames. We built a bridge that catches C++ exceptions, stores them in thread-local storage, and rethrows them as Swift errors after each call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance benchmarks
&lt;/h2&gt;

&lt;p&gt;Our goal was simple: don't sacrifice performance for better Swift APIs. Turbo Modules set the bar for modern React Native native modules, and we wanted to match that performance while providing superior ergonomics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Methodology
&lt;/h3&gt;

&lt;p&gt;We tested four micro-benchmarks across three native module architectures on two SDK versions. Each benchmark ran 100,000 iterations, averaged across three trials on an iPhone 16 Pro release build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sync no-op function&lt;/li&gt;
&lt;li&gt;Adding two numbers (0 + 1)
&lt;/li&gt;
&lt;li&gt;String concatenation ('hello' + 'world')&lt;/li&gt;
&lt;li&gt;Async no-op function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architectures tested were Expo Modules, React Native Turbo Modules, and the legacy Bridge. We used trivial inputs intentionally - these measure boundary crossing costs, not computation costs.&lt;/p&gt;

&lt;p&gt;Note on async testing: Expo Modules use Swift Concurrency (&lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;), which requires more work per call than callback-style async (Task creation, continuations, scheduler interaction). Turbo Modules and Bridge use callbacks. This compares the same logical operation done idiomatically in each system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;CODE_BLOCK_N&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Expo Modules became 1.6-2.3x faster across all benchmarks. The improvements match our architectural changes: boundary costs dominated the no-op test, marshaling costs affected strings most, and async showed the largest absolute gains due to removed overhead.&lt;/p&gt;

&lt;p&gt;Before SDK 56, Expo Modules trailed Turbo Modules on every test. After the rewrite, we match Turbo Modules on simple sync calls and lead by 55% on async operations. The async advantage matters most in real apps where promises chain across module boundaries.&lt;/p&gt;

&lt;p&gt;Turbo Modules also improved between SDK 55 and 56 from upstream React Native changes, so we were catching up to a moving target.&lt;/p&gt;

&lt;p&gt;The Bridge results show the old story: 3-4x slower on sync operations due to JSON serialization overhead. The async gap narrows to 1.6x because Promise allocation and scheduling costs affect all architectures similarly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;p&gt;These micro-benchmarks measure boundary crossing costs. Real app performance depends on call frequency, payload size, and actual work being done. Device differences, OS versions, and Hermes builds will shift absolute numbers, but the performance ratios should remain consistent.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;Removing the Objective-C++ layer makes previously difficult features straightforward to implement. It also opens up performance optimizations that are now practical with a single-language call path.&lt;/p&gt;

&lt;p&gt;This rewrite provides the foundation for the next round of API improvements we're planning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using SDK 56 native modules
&lt;/h2&gt;

&lt;p&gt;SDK 56 ships the new native module architecture on iOS, tvOS, and macOS. Check the &lt;a href="https://expo.dev/changelog/sdk-56" rel="noopener noreferrer"&gt;SDK 56 release notes&lt;/a&gt; for complete details. The &lt;code&gt;expo-modules-jsi&lt;/code&gt; package is available on &lt;a href="https://github.com/expo/expo/tree/main/packages/expo-modules-jsi" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; for bug reports, feature requests, and contributions.&lt;/p&gt;

&lt;p&gt;Android takes a different approach in SDK 56. The major win there is our Kotlin compiler plugin, which moves more work to compile time and delivers larger performance gains than a JSI rewrite would provide. We may explore a Kotlin-first JSI wrapper eventually, but Android's JSI performance was already in better shape.&lt;/p&gt;

&lt;p&gt;One final note: AI significantly accelerated this rewrite. It covered almost the entire JSI C++ surface in Swift and pushed test coverage to nearly 90%. Doing this work manually would have taken much longer.&lt;/p&gt;

&lt;p&gt;This post is based on content from the &lt;a href="https://expo.dev/blog/talking-to-jsi-in-swift" rel="noopener noreferrer"&gt;Expo blog&lt;/a&gt;. Follow &lt;a href="https://dev.to/expo"&gt;@expo&lt;/a&gt; for more React Native content.&lt;/p&gt;

</description>
      <category>expo</category>
      <category>mobile</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>Control SwiftUI and Compose State Synchronously with Worklets in Expo UI</title>
      <dc:creator>Dan</dc:creator>
      <pubDate>Thu, 28 May 2026 20:20:50 +0000</pubDate>
      <link>https://dev.to/expo/control-swiftui-and-compose-state-synchronously-with-worklets-in-expo-ui-3ado</link>
      <guid>https://dev.to/expo/control-swiftui-and-compose-state-synchronously-with-worklets-in-expo-ui-3ado</guid>
      <description>&lt;p&gt;React Native developers have long dealt with the friction of bridging JavaScript with native UI threads. Every time you need to update native state, you send a message across the bridge, wait for the round-trip, and hope the user doesn't notice the delay. Expo UI in SDK 56 changes this with worklet integration.&lt;/p&gt;

&lt;p&gt;You can now control SwiftUI and Compose state directly on the UI thread, with zero JavaScript round-trips. Here's what that looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TextInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useNativeState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@expo/ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useNativeState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Host&lt;/span&gt; &lt;span class="nx"&gt;matchContents&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TextInput&lt;/span&gt;
        &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Type something&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;onChangeText&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;worklet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="c1"&gt;// Runs synchronously on the UI thread, on every keystroke.&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[UI thread] typed:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}}&lt;/span&gt;
      &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Host&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: you'll need &lt;code&gt;react-native-reanimated&lt;/code&gt; and &lt;code&gt;react-native-worklets&lt;/code&gt; installed in your project for this to work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How this actually works
&lt;/h2&gt;

&lt;p&gt;Two pieces make this possible:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;useNativeState&lt;/code&gt; creates an &lt;code&gt;ObservableState&lt;/code&gt; - a &lt;code&gt;SharedObject&lt;/code&gt; that lives in native code and gets observed by both SwiftUI and Compose. On iOS, it maps to an &lt;code&gt;ObservableObject&lt;/code&gt;. On Android, it's a &lt;code&gt;MutableState&lt;/code&gt;. Both platforms watch this state and re-render when it changes.&lt;/p&gt;

&lt;p&gt;Worklet callbacks like &lt;code&gt;onTextChange&lt;/code&gt; run directly on the UI thread when the native view fires its event. No bridge crossing required.&lt;/p&gt;

&lt;p&gt;Put them together: each keystroke in the &lt;code&gt;TextField&lt;/code&gt; updates the shared &lt;code&gt;text&lt;/code&gt; state, executes your worklet, and triggers SwiftUI and Compose to re-render. All on the UI thread, all in the same frame.&lt;/p&gt;

&lt;p&gt;If you know SwiftUI, this pattern should click immediately. The TypeScript above translates almost directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;Screen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;

  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Type something"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;$text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newValue&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[UI thread] typed:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;useNativeState&lt;/code&gt; acts like &lt;code&gt;@State&lt;/code&gt;, &lt;code&gt;text={text}&lt;/code&gt; works like &lt;code&gt;TextField(text: $text)&lt;/code&gt;, and the worklet &lt;code&gt;onTextChange&lt;/code&gt; behaves like &lt;code&gt;.onChange(of:)&lt;/code&gt;. Compose developers will recognize the same shape with &lt;code&gt;mutableStateOf&lt;/code&gt; and &lt;code&gt;onValueChange&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Input masking without flicker
&lt;/h2&gt;

&lt;p&gt;The immediate win here is input masking that actually works. Since the worklet can modify &lt;code&gt;text.value&lt;/code&gt; in the same frame as the keystroke, users never see the unmasked character. No async delays, no visible corrections.&lt;/p&gt;

&lt;p&gt;Take this credit card field that formats &lt;code&gt;4242424242424242&lt;/code&gt; into &lt;code&gt;4242 4242 4242 4242&lt;/code&gt; as you type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TextInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useNativeState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@expo/ui/swift-ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CardNumberField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useNativeState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Host&lt;/span&gt; &lt;span class="nx"&gt;matchContents&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TextInput&lt;/span&gt;
        &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Card number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;onChangeText&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;worklet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sr"&gt;D/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;masked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;.&lt;/span&gt;&lt;span class="se"&gt;{4})&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$1 &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;masked&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}}&lt;/span&gt;
      &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Host&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The formatting happens instantly on the UI thread. You can use this pattern for phone numbers, dates, currency formatting, or any scenario where display text needs to differ from raw input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond input masking
&lt;/h2&gt;

&lt;p&gt;Worklet integration does more than solve input problems. It gives Expo UI a path to expose synchronous alternatives alongside existing async APIs. You pick the approach that fits your interaction needs.&lt;/p&gt;

&lt;p&gt;This same native state + worklet pattern opens up much more of SwiftUI and Compose's state-driven APIs for React Native. Input masking is just the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Worklet support works in both &lt;code&gt;@expo/ui/swift-ui&lt;/code&gt; and &lt;code&gt;@expo/ui/jetpack-compose&lt;/code&gt;. It shipped in SDK 56. &lt;code&gt;TextInput&lt;/code&gt; supports sync callbacks now, with more form controls coming in future releases.&lt;/p&gt;

&lt;p&gt;This post is based on content from the &lt;a href="https://expo.dev/blog/worklet-integration-in-expo-ui-synchronously-controlling-swiftui-and-compose-state" rel="noopener noreferrer"&gt;Expo blog&lt;/a&gt;. Follow &lt;a href="https://dev.to/expo"&gt;@expo&lt;/a&gt; for more React Native content.&lt;/p&gt;

</description>
      <category>expo</category>
      <category>mobile</category>
      <category>javascript</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>How to upgrade to Expo SDK 56</title>
      <dc:creator>Dan</dc:creator>
      <pubDate>Sat, 23 May 2026 13:32:39 +0000</pubDate>
      <link>https://dev.to/expo/how-to-upgrade-to-expo-sdk-56-o3b</link>
      <guid>https://dev.to/expo/how-to-upgrade-to-expo-sdk-56-o3b</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://expo.dev/blog/upgrading-to-sdk-56" rel="noopener noreferrer"&gt;expo.dev/blog&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;By Keith Kurak&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;Expo SDK 56 is here, with support for React Native 0.85, and React 19.2. Android API level 36 and Xcode 26.4 and higher are supported. Also unchanged are the minimum supported operating systems: SDK 56 can build apps for Android 7+ and iOS 16.4 and higher. Check out the &lt;a href="https://expo.dev/changelog/sdk-56" rel="noopener noreferrer"&gt;changelog&lt;/a&gt; to get a full picture of all that’s included. And here’s a short highlight video:&lt;/p&gt;

&lt;p&gt;Each SDK undergoes extensive testing and a beta test period, where the Expo team and the community collaborate to find issues that might stand in the way of a fast and smooth upgrade for others. A lot of Expo engineers maintain our own apps and try to upgrade those as soon as the beta is out.&lt;/p&gt;

&lt;p&gt;Nonetheless, there’s practically infinite possibilities out there. Every app is unique, and has its own complexities that will need to be accounted for when upgrading. Therefore, we wanted to highlight some specific key changes that may affect your upgrade to SDK 56, as well as some evergreen advice when it comes to upgrading to the latest Expo SDK.&lt;/p&gt;
&lt;h2&gt;
  
  
  Key things to know as you upgrade to SDK 56
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Power your SDK upgrade with Claude Code
&lt;/h3&gt;

&lt;p&gt;At Expo, we use Claude Code in our day-to-day work. Based on our experiences, we’ve published some skills to help with common tasks within Expo apps in the &lt;a href="https://docs.expo.dev/skills/" rel="noopener noreferrer"&gt;expo/skills library&lt;/a&gt;. One of these skills is for &lt;a href="https://github.com/expo/skills/tree/main/plugins/upgrading-expo" rel="noopener noreferrer"&gt;upgrading to the latest Expo SDK version&lt;/a&gt;. In addition to the basics like updating package versions, it handles things like breaking changes, cleaning up outdated configurations, and more.&lt;/p&gt;

&lt;p&gt;Within Claude Code on your terminal, run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin marketplace add expo/skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to add the skills marketplace to Claude, and then&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin &lt;span class="nb"&gt;install &lt;/span&gt;expo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After restarting Claude, you can ask it in plain language to upgrade your SDK. If it’s installed correctly, you should see Claude reference the skill as it gets started:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F787pl6i4faotqt5wmcrv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F787pl6i4faotqt5wmcrv.png" alt="Claude prompt showing usage of Expo upgrade skill" width="658" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also import Expo skills into other agents via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bunx skills add expo/skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As always, do your work on a separate branch, and review your code before merge and deploy. LLMs are amazing, but Claude and the Expo upgrade skill can’t be aware of every possible scenario. Human developers are a vital part of this workflow!&lt;/p&gt;

&lt;h3&gt;
  
  
  Faster native builds
&lt;/h3&gt;

&lt;p&gt;To speed up iOS builds, SDK 56 ships &lt;a href="https://docs.expo.dev/guides/prebuilt-expo-modules/" rel="noopener noreferrer"&gt;prebuilt XCFrameworks&lt;/a&gt; for our most complex Expo modules on iOS. This is enabled by default both locally and on EAS Build — no configuration required. To opt out, set the &lt;code&gt;EXPO_USE_PRECOMPILED_MODULES&lt;/code&gt; environment variable to &lt;code&gt;0&lt;/code&gt; (for local builds), and also as an &lt;a href="https://docs.expo.dev/eas/environment-variables/manage/" rel="noopener noreferrer"&gt;EAS environment variable&lt;/a&gt; (for EAS Build).&lt;/p&gt;

&lt;p&gt;For Android, a new opt-in &lt;code&gt;android.usePrecompiledHeaders&lt;/code&gt; option in &lt;code&gt;[expo-build-properties](https://docs.expo.dev/versions/v56.0.0/sdk/build-properties/)&lt;/code&gt; applies CMake precompiled headers to the C++ codegen output for every autolinked native module, dramatically cutting CMake compile times on Android.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expo Go update
&lt;/h3&gt;

&lt;p&gt;Expo Go for SDK 56 is not available on the Apple App Store or Google Play Store. We do not have a timeline for when they will be, and we will update the SDK 56 changelog when we have more news to share. For more information, you can refer to “&lt;a href="https://expo.dev/changelog/expo-go-and-app-store-may-2026" rel="noopener noreferrer"&gt;Expo Go and the App Store in May 2026&lt;/a&gt;.”&lt;/p&gt;

&lt;p&gt;The Expo Go app is our tool for getting started quickly, it's an educational tool to help you learn to build on mobile. We encourage you to take advantage of this transition period to migrate your project to using a &lt;a href="https://docs.expo.dev/develop/development-builds/expo-go-to-dev-build/" rel="noopener noreferrer"&gt;development build&lt;/a&gt;, which provides you with everything that you need to build an app that you ship to stores.&lt;/p&gt;

&lt;p&gt;You can install Expo Go for SDK 56 from Expo CLI directly on Android devices. For iOS, you can use the &lt;a href="https://testflight.apple.com/join/GZJxxfUU" rel="noopener noreferrer"&gt;TestFlight External Beta&lt;/a&gt; or the &lt;code&gt;eas go&lt;/code&gt; command to create an Expo Go build for SDK 56 and upload it to your own TestFlight team.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hermes bytecode diffing for updates is enabled by default
&lt;/h3&gt;

&lt;p&gt;In SDK 55 we &lt;a href="https://expo.dev/changelog/sdk-55#hermes-bytecode-diffing-for-eas-update-and-expo-updates" rel="noopener noreferrer"&gt;introduced opt-in Hermes bytecode diffing&lt;/a&gt; for &lt;code&gt;expo-updates&lt;/code&gt; and &lt;a href="https://expo.dev/services#update" rel="noopener noreferrer"&gt;EAS Update&lt;/a&gt;: instead of downloading a full bundle on every update, the client downloads a binary patch against the previously installed bytecode.&lt;/p&gt;

&lt;p&gt;Diffing is on by default in SDK 56. To opt out, set &lt;code&gt;"enableBsdiffPatchSupport": false&lt;/code&gt; in the &lt;code&gt;updates&lt;/code&gt; block of &lt;strong&gt;app.json&lt;/strong&gt;. &lt;a href="https://docs.expo.dev/versions/v56.0.0/sdk/updates/" rel="noopener noreferrer"&gt;Learn more in the&lt;/a&gt;&amp;nbsp;&lt;code&gt;[expo-updates](https://docs.expo.dev/versions/v56.0.0/sdk/updates/)&lt;/code&gt;&amp;nbsp;&lt;a href="https://docs.expo.dev/versions/v56.0.0/sdk/updates/" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inline Expo Modules
&lt;/h3&gt;

&lt;p&gt;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. 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 &lt;a href="https://docs.expo.dev/modules/inline-modules-reference/" rel="noopener noreferrer"&gt;reference&lt;/a&gt; and the &lt;a href="https://docs.expo.dev/modules/inline-modules-tutorial/" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt; for more information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips for upgrading your Expo SDK
&lt;/h2&gt;

&lt;p&gt;We have written up &lt;a href="https://github.com/expo/fyi/blob/main/troubleshooting-sdk-upgrades.md" rel="noopener noreferrer"&gt;detailed advice for troubleshooting issues found during an upgrade&lt;/a&gt;. This includes considerations for both before and during your upgrade, with a list of suggestions, starting with the quickest/easiest to try. We recommend reading the entire guide, but we wanted to highlight a few key items in brief here:&lt;/p&gt;

&lt;h3&gt;
  
  
  Use the upgrade skill!
&lt;/h3&gt;

&lt;p&gt;It’s so nice, we’ll say it twice: Add the &lt;a href="https://docs.expo.dev/skills/" rel="noopener noreferrer"&gt;expo/skills library&lt;/a&gt; to your preferred LLM and let it do the upgrade for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshoot with Expo MCP
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.expo.dev/eas/ai/mcp" rel="noopener noreferrer"&gt;Expo MCP&lt;/a&gt; now includes more tools to help you troubleshoot your upgrade alongside Claude Code, Codex, Cursor, and others. If one of your EAS builds fails, use the &lt;code&gt;build_logs&lt;/code&gt; tool to pull down the build logs for analysis with AI. If you install the &lt;code&gt;expo-mcp&lt;/code&gt; package, you can use the &lt;code&gt;collect_app_logs&lt;/code&gt; tool to pull native logcat / macOS console logs from your emulator/simulator.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the changelog
&lt;/h3&gt;

&lt;p&gt;Most SDK releases will have a list of known breaking changes, or notable changes where you may need to tweak configuration for a scenario specific to your app. The best time to read the &lt;a href="https://expo.dev/changelog/sdk-56" rel="noopener noreferrer"&gt;changelog&lt;/a&gt; and breaking changes is before you upgrade, so you can make those tweaks before you test, but the next best time to read it is after you upgrade, particularly if you see a compilation error or crash.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using development builds over Expo Go
&lt;/h3&gt;

&lt;p&gt;Upgrades are best taken when you don’t feel rushed to complete them. As Expo Go automatically upgraded to the latest version after the SDK release on your phone, you may have noticed that your app no longer worked in Expo Go, and felt that you needed to upgrade right away in order to keep working on features.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.expo.dev/develop/development-builds/introduction/" rel="noopener noreferrer"&gt;Development builds&lt;/a&gt; help reduce the temperature, giving you time and space to take on an upgrade while not interrupting ongoing feature work. A development build works a lot like Expo Go, allowing you to scan a QR code to work on your code locally without rebuilding. But it’s your own app, so it will not get upgraded when a new version of Expo Go is released.&lt;/p&gt;

&lt;p&gt;If you still feel that you need to use Expo Go, know that you don’t necessarily have to use the latest version on the Play and App Stores. You can go to &lt;a href="https://expo.dev/go" rel="noopener noreferrer"&gt;https://expo.dev/go&lt;/a&gt; and download previous versions for use on Android devices and iOS simulators. Unfortunately, due to App Store restrictions, this does not work on iOS devices.&lt;/p&gt;

&lt;p&gt;Still, another &lt;a href="https://expo.dev/blog/expo-go-vs-development-builds" rel="noopener noreferrer"&gt;reason to migrate to a development build&lt;/a&gt; is because Expo Go is quite limited in how it can replicate your production app, leading to issues where it works in Expo Go but not when you build your production app. Expo Go can run your JavaScript, but it cannot apply most of your app.json / app.config.js configuration, because that would require modifying native code. In short, Expo Go can’t contain nearly everything that’s unique and special about your app. Development builds can. There's plenty of headroom in the &lt;a href="https://expo.dev/pricing" rel="noopener noreferrer"&gt;Free plan&lt;/a&gt; to make some development builds, or you can build locally &lt;code&gt;npx expo run:android&lt;/code&gt; or &lt;code&gt;npx expo run:ios&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  New Architecture advice
&lt;/h3&gt;

&lt;p&gt;Expo SDK 54 was the last SDK to support the Old Architecture. SDK 55+ / React Native 0.83+ only support New Architecture. Therefore, if you haven’t upgraded to the New Architecture yet, now is definitely the time! A number of the latest versions of major packages, such as &lt;code&gt;react-native-reanimated&lt;/code&gt; v4 and &lt;code&gt;@shopify/flash-list&lt;/code&gt; v4, only support New Architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid upgrading both your Expo SDK and adopting New Architecture at the same time.&lt;/strong&gt; This makes it more difficult to isolate any issues. Compared to upgrading your Expo SDK, adopting New Architecture is the bigger change, so any issues are likely related to that- but it will be hard to tell if you upgrade to both at the same time.&lt;/p&gt;

&lt;p&gt;We recommend first &lt;a href="https://docs.expo.dev/guides/new-architecture/#enable-the-new-architecture-in-an-existing-project" rel="noopener noreferrer"&gt;upgrading to the New Architecture&lt;/a&gt; on SDK 54 and then creating a development build. Test that, using our &lt;a href="https://docs.expo.dev/guides/new-architecture/#troubleshooting" rel="noopener noreferrer"&gt;New Architecture troubleshooting guide to help&lt;/a&gt; if you run into any issues. After you’ve verified things are working just by upgrading to New Architecture, upgrade to SDK 56 and make a new development build. Now, you’ll just be testing your SDK 56 upgrade in isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the troubleshooting guides
&lt;/h3&gt;

&lt;p&gt;We have a landing page of our &lt;a href="https://docs.expo.dev/troubleshooting/overview/" rel="noopener noreferrer"&gt;most popular troubleshooting guides&lt;/a&gt; that you can browse depending on your issue. If your issue is an error when building, you’ll want to take different steps compared to a crash or performance issue. Even if you don’t fully get to the root cause, &lt;a href="https://docs.expo.dev/debugging/runtime-issues/#production-app-is-crashing" rel="noopener noreferrer"&gt;using a tool like ADB Logcat or macOS console&lt;/a&gt; to find a native error that the operating system reports from a crash can be very helpful as you engage in further troubleshooting or ask others for help.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reach out if you need a hand!
&lt;/h3&gt;

&lt;p&gt;We appreciate your bug reports and feedback! The best way to surface an issue is and will always be a Github issue with a &lt;strong&gt;minimal reproduction&lt;/strong&gt;, where you send us a link to a Github repo based on the default project template created with &lt;code&gt;npx create-expo-app&lt;/code&gt; , plus just enough code to reproduce the issue.&lt;/p&gt;

&lt;p&gt;A minimal reproduction ensures that our team can see exactly what you’re seeing and gives us a way to test that our fix will work for you. Even if that seems like a lot to do, often spending 15 or 30 minutes trying to make a minimal reproduction is more effective than hours spent debugging on your actual app, where there are a lot of moving pieces and it’s more difficult to isolate issues. AI tools like Claude can even help make the minimal reproduction for you.&lt;/p&gt;

&lt;p&gt;We also understand the value in discussing an issue in the moment, even before you’re ready to try to reproduce it. Other developers may be experiencing the same thing and already have an answer. Discussions about issues on &lt;a href="https://discord.com/invite/expo" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;, &lt;a href="https://www.reddit.com/r/expo/" rel="noopener noreferrer"&gt;Reddit&lt;/a&gt;, &lt;a href="https://bsky.app/profile/expo.dev" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt; and elsewhere can result in a sort of &lt;a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging" rel="noopener noreferrer"&gt;collaborative virtual rubberducking&lt;/a&gt; where we find the answer together while talking through it.&lt;/p&gt;

&lt;p&gt;We encourage you to post screenshots or videos of the issues you’re facing, or at least descriptions of what exactly you’re seeing, what platforms are affected, etc. so we can see what is broken and help the community think through how to isolate that issue and find a solution. If you have detailed feedback about the upgrade process that doesn’t fit neatly into a minimal reproduction of a single issue, we’d love to hear about it, as well. Besides social forums, we always have someone checking on the messages received from our &lt;a href="https://expo.dev/support" rel="noopener noreferrer"&gt;support page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy upgrading and we hope you love SDK 56!&lt;/p&gt;

</description>
      <category>expo</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Expo SDK 56</title>
      <dc:creator>Dan</dc:creator>
      <pubDate>Thu, 21 May 2026 18:21:13 +0000</pubDate>
      <link>https://dev.to/expo/expo-sdk-56-5eb5</link>
      <guid>https://dev.to/expo/expo-sdk-56-5eb5</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://expo.dev/changelog/sdk-56" rel="noopener noreferrer"&gt;expo.dev/changelog&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;By Alan Hughes, Brent Vatne&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;Today we're announcing the release of Expo SDK 56. SDK 56 includes &lt;a href="https://reactnative.dev/blog/2026/04/07/react-native-0.85" rel="noopener noreferrer"&gt;React Native 0.85&lt;/a&gt; and &lt;a href="https://react.dev/blog/2025/10/01/react-19-2" rel="noopener noreferrer"&gt;React 19.2&lt;/a&gt;. Thank you to everyone who helped with beta testing.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Expo UI is now ready for production&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;[create-expo-app](https://docs.expo.dev/more/create-expo/)&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;
&lt;h3&gt;
  
  
  Universal components
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpewn43oalht3otvajwdd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpewn43oalht3otvajwdd.png" alt="Universal field group settings UI" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The universal components are backed by &lt;code&gt;[@expo/ui/jetpack-compose](https://docs.expo.dev/versions/v56.0.0/sdk/ui/jetpack-compose/)&lt;/code&gt; on Android, &lt;code&gt;[@expo/ui/swift-ui](https://docs.expo.dev/versions/v56.0.0/sdk/ui/swift-ui/)&lt;/code&gt; on iOS, and &lt;code&gt;react-dom&lt;/code&gt; or &lt;code&gt;react-native-web&lt;/code&gt; on web. You can now build more cross-platform UI with Expo UI without splitting files into &lt;code&gt;.android.tsx&lt;/code&gt; and &lt;code&gt;.ios.tsx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Universal components include layout primitives, text, inputs, controls, and sheets such as &lt;code&gt;Host&lt;/code&gt;, &lt;code&gt;Row&lt;/code&gt;, &lt;code&gt;Column&lt;/code&gt;, &lt;code&gt;ScrollView&lt;/code&gt;, &lt;code&gt;Text&lt;/code&gt;, &lt;code&gt;TextInput&lt;/code&gt;, &lt;code&gt;Button&lt;/code&gt;, &lt;code&gt;Switch&lt;/code&gt;, &lt;code&gt;Slider&lt;/code&gt;, &lt;code&gt;Checkbox&lt;/code&gt;, and &lt;code&gt;BottomSheet&lt;/code&gt;. &lt;a href="https://docs.expo.dev/versions/v56.0.0/sdk/ui/universal/" rel="noopener noreferrer"&gt;Learn more&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Stable native APIs
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extend Expo UI with custom views and modifiers&lt;/strong&gt;: 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: &lt;a href="https://docs.expo.dev/guides/expo-ui-swift-ui/extending/" rel="noopener noreferrer"&gt;SwiftUI guide&lt;/a&gt; and &lt;a href="https://docs.expo.dev/guides/expo-ui-jetpack-compose/extending/" rel="noopener noreferrer"&gt;Compose guide&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Material 3 Dynamic Colors and the Material Symbols catalog&lt;/strong&gt;: the new &lt;code&gt;[useMaterialColors](https://docs.expo.dev/versions/v56.0.0/sdk/ui/jetpack-compose/colors/)&lt;/code&gt; hook hands you Material 3 Dynamic Colors that follow the system theme, and the &lt;code&gt;[Icon](https://docs.expo.dev/versions/v56.0.0/sdk/ui/jetpack-compose/icon/)&lt;/code&gt; component pairs with &lt;code&gt;[@expo/material-symbols](https://www.npmjs.com/package/@expo/material-symbols)&lt;/code&gt; to bring the full Material Symbols catalog within import reach.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;**react-native-worklets**&lt;/code&gt;** integration and native state**: Expo UI now integrates with &lt;code&gt;react-native-worklets&lt;/code&gt; and native state primitives from the underlying UI frameworks: &lt;code&gt;[ObservableObject](https://developer.apple.com/documentation/combine/observableobject)&lt;/code&gt; on SwiftUI and &lt;code&gt;[MutableState](https://developer.android.com/reference/kotlin/androidx/compose/runtime/MutableState)&lt;/code&gt; on Jetpack Compose. The new &lt;code&gt;useNativeState&lt;/code&gt; hook lets JavaScript control that native state directly, which is useful for native state-driven animations and form controls. Learn more about &lt;code&gt;useNativeState&lt;/code&gt; for &lt;a href="https://docs.expo.dev/versions/v56.0.0/sdk/ui/swift-ui/usenativestate/" rel="noopener noreferrer"&gt;SwiftUI&lt;/a&gt; and &lt;a href="https://docs.expo.dev/versions/v56.0.0/sdk/ui/jetpack-compose/usenativestate/" rel="noopener noreferrer"&gt;Jetpack Compose&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Synchronous worklet callbacks&lt;/strong&gt;: a new &lt;code&gt;WorkletCallback&lt;/code&gt; shared object allows synchronous UI worklet callbacks to be passed as props to Expo UI views on both platforms. &lt;code&gt;TextField&lt;/code&gt; on iOS and Compose can now use native state for &lt;code&gt;value&lt;/code&gt;, and &lt;code&gt;onValueChange&lt;/code&gt; accepts &lt;code&gt;WorkletCallback&lt;/code&gt;, enabling synchronous, flicker-free controlled text inputs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Components, modifiers, and API changes&lt;/strong&gt;: SDK 56 lands the bulk of the stabilization work since SDK 55. See the &lt;code&gt;[@expo/ui](https://github.com/expo/expo/blob/main/packages/expo-ui/CHANGELOG.md)&lt;/code&gt;&lt;a href="https://github.com/expo/expo/blob/main/packages/expo-ui/CHANGELOG.md" rel="noopener noreferrer"&gt; CHANGELOG&lt;/a&gt; for the full list.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Drop-in replacements for community components
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;For example, you can migrate from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;DateTimePicker&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@react-native-community/datetimepicker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;DateTimePicker&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@expo/ui/community/datetime-picker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop-in replacements are available for &lt;code&gt;@react-native-segmented-control/segmented-control&lt;/code&gt;, &lt;code&gt;@react-native-picker/picker&lt;/code&gt;, &lt;code&gt;@react-native-community/datetimepicker&lt;/code&gt;, &lt;code&gt;@react-native-masked-view/masked-view&lt;/code&gt;, and &lt;code&gt;@gorhom/bottom-sheet&lt;/code&gt; APIs. Most migrations only require changing the import, though some props may differ because Expo UI is backed by SwiftUI and Jetpack Compose rather than UIKit and Android Views. &lt;a href="https://docs.expo.dev/versions/v56.0.0/sdk/ui/drop-in-replacements/" rel="noopener noreferrer"&gt;Learn more&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Faster native builds&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Precompiled Expo packages on iOS
&lt;/h3&gt;

&lt;p&gt;SDK 56 ships &lt;a href="https://docs.expo.dev/guides/prebuilt-expo-modules/" rel="noopener noreferrer"&gt;prebuilt XCFrameworks&lt;/a&gt; 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 &lt;code&gt;EXPO_USE_PRECOMPILED_MODULES&lt;/code&gt; environment variable to &lt;code&gt;0&lt;/code&gt; (for local builds), and also as an &lt;a href="https://docs.expo.dev/eas/environment-variables/manage/" rel="noopener noreferrer"&gt;EAS environment variable&lt;/a&gt; (for EAS Build).&lt;/p&gt;

&lt;h3&gt;
  
  
  Precompiled headers for Android codegen (experimental)
&lt;/h3&gt;

&lt;p&gt;A new opt-in &lt;code&gt;android.usePrecompiledHeaders&lt;/code&gt; option in &lt;code&gt;[expo-build-properties](https://docs.expo.dev/versions/v56.0.0/sdk/build-properties/)&lt;/code&gt; 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 &lt;code&gt;:app:buildCMakeDebug&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4sni6r1lmfmgxw3v379o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4sni6r1lmfmgxw3v379o.png" alt="Android codegen compilation cut by nearly two thirds: :app:buildCMakeDebug went from 17m 10.724s to 6m 06.692s, a 2.81x speedup and 11m 04s saved" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enable it in &lt;strong&gt;app.json&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;app.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"expo-build-properties"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"android"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"usePrecompiledHeaders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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. &lt;a href="https://github.com/expo/expo/pull/45922" rel="noopener noreferrer"&gt;Learn more in the PR&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Expo Modules: easier to write, faster to run&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Inline modules
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;You can develop inline modules from Android Studio, Xcode, or any other IDE as they are part of your project structure.&lt;/p&gt;

&lt;p&gt;Check out the inline modules &lt;a href="https://docs.expo.dev/modules/inline-modules-reference/" rel="noopener noreferrer"&gt;reference&lt;/a&gt; and the &lt;a href="https://docs.expo.dev/modules/inline-modules-tutorial/" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt; for more information!&lt;/p&gt;

&lt;h3&gt;
  
  
  Type generation tools
&lt;/h3&gt;

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

&lt;p&gt;It also includes a CLI with the following key commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;module-interface&lt;/code&gt;: 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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;[ModuleName]Types.ts&lt;/strong&gt;: contains all type declarations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;[ModuleName]Module.ts&lt;/strong&gt;: contains the module class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;[ModuleName]View.tsx&lt;/strong&gt;: exports the default view component(s) with typed props.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;index.ts&lt;/strong&gt;: re-exports the module alongside every defined type and view.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;inline-modules-interface&lt;/code&gt;: generates a pair of TypeScript files (&lt;strong&gt;generated&lt;/strong&gt;, &lt;strong&gt;stable&lt;/strong&gt;) for each Swift inline module in a project.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;short-module-interface&lt;/code&gt;: works similarly to &lt;code&gt;inline-modules-interface&lt;/code&gt;, but targets a specific Swift module instead of all inline modules in the project.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;All of these commands can be run in a watch mode to automatically regenerate the TypeScript interfaces.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.expo.dev/modules/type-generation-reference/" rel="noopener noreferrer"&gt;Learn more in the &lt;/a&gt;&lt;code&gt;[expo-type-information](https://docs.expo.dev/modules/type-generation-reference/)&lt;/code&gt;&lt;a href="https://docs.expo.dev/modules/type-generation-reference/" rel="noopener noreferrer"&gt; reference&lt;/a&gt; and &lt;a href="https://docs.expo.dev/modules/type-generation-tutorial/" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Revamped &lt;code&gt;create-expo-module&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;In SDK 56, &lt;code&gt;[create-expo-module](https://docs.expo.dev/more/create-expo-module/)&lt;/code&gt; has been revamped for improved stability and a richer feature-set.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;New **`&lt;/strong&gt;create-expo-module*&lt;em&gt;`&lt;/em&gt;* skill**: helps agents create Expo modules â€” &lt;a href="https://github.com/expo/skills/pull/49" rel="noopener noreferrer"&gt;coming soon&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;New **`&lt;/strong&gt;addPlatformSupport*&lt;em&gt;`&lt;/em&gt;* 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.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Modular template&lt;/strong&gt;: when creating a module you can pick which features get scaffolded and which platforms it targets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Non-interactive mode support&lt;/strong&gt;: field defaults have been improved and some fields are no longer required; in non-interactive mode, &lt;code&gt;create-expo-module&lt;/code&gt; logs the defaults that were used.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No barrel file by default&lt;/strong&gt;: local modules no longer use &lt;code&gt;index.ts&lt;/code&gt;; pass &lt;code&gt;--barrel&lt;/code&gt; to opt in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Windows support&lt;/strong&gt;: &lt;code&gt;create-expo-module&lt;/code&gt; now works well on Windows.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Runtime performance improvements in &lt;code&gt;expo-modules-core&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Kotlin compiler plugin&lt;/strong&gt; â€” A new Kotlin compiler plugin replaces reflection with build-time code generation for Expo Modules on Android. &lt;a href="https://x.com/lkosmaty/status/2051693879770964433" rel="noopener noreferrer"&gt;In our benchmarks&lt;/a&gt;, 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcscc29p87q4mnewi2d05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcscc29p87q4mnewi2d05.png" alt="Activity.onCreate is 1.7x faster (55 ms vs 93 ms on SDK 55), and Time to Interactive is 1.5x faster (531 ms vs 797 ms on SDK 55)" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;New JSI layer for iOS native modules&lt;/strong&gt; â€” 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;React Native 0.85 and React 19.2&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Expo SDK 55 included React Native 0.83, so be sure to refer to the full release notes for &lt;a href="https://reactnative.dev/blog/2026/02/11/react-native-0.84" rel="noopener noreferrer"&gt;0.84&lt;/a&gt; and &lt;a href="https://reactnative.dev/blog/2026/04/07/react-native-0.85" rel="noopener noreferrer"&gt;0.85&lt;/a&gt; for the complete picture. A few highlights include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hermes v1 by default&lt;/strong&gt;: 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 &lt;code&gt;[useHermesV1](https://docs.expo.dev/versions/v56.0.0/sdk/build-properties/#sharedbuildconfigfields)&lt;/code&gt; configuration in &lt;code&gt;[expo-build-properties](https://docs.expo.dev/versions/v56.0.0/sdk/build-properties/)&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;New animation backend&lt;/strong&gt;: 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.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HTTPS dev server&lt;/strong&gt;: the Metro dev server now supports HTTPS via TLS configuration, enabling secure local development environments and compatibility with APIs that require secure origins.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Node.js minimum bump&lt;/strong&gt;: React Native 0.85 drops support for Node.js versions before v20.19.4.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Hermes bytecode diffing is now enabled by default&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In SDK 55 we &lt;a href="https://expo.dev/changelog/sdk-55#hermes-bytecode-diffing-for-eas-update-and-expo-updates" rel="noopener noreferrer"&gt;introduced opt-in Hermes bytecode diffing&lt;/a&gt; for &lt;code&gt;expo-updates&lt;/code&gt; and &lt;a href="https://expo.dev/services#update" rel="noopener noreferrer"&gt;EAS Update&lt;/a&gt;: 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 &lt;strong&gt;58% smaller&lt;/strong&gt; than the full bundle they replaced.&lt;/p&gt;

&lt;p&gt;Diffing is on by default in SDK 56. To opt out, set &lt;code&gt;"enableBsdiffPatchSupport": false&lt;/code&gt; in the &lt;code&gt;updates&lt;/code&gt; block of &lt;strong&gt;app.json&lt;/strong&gt;. &lt;a href="https://docs.expo.dev/versions/v56.0.0/sdk/updates/" rel="noopener noreferrer"&gt;Learn more in the &lt;/a&gt;&lt;code&gt;[expo-updates](https://docs.expo.dev/versions/v56.0.0/sdk/updates/)&lt;/code&gt;&lt;a href="https://docs.expo.dev/versions/v56.0.0/sdk/updates/" rel="noopener noreferrer"&gt; API reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We're also working on extending bytecode diffing to patch against the &lt;strong&gt;embedded bundle shipped in your native build&lt;/strong&gt;, 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;More capable **`&lt;/strong&gt;expo-file-system**`
&lt;/h2&gt;

&lt;p&gt;SDK 56 fills several parity gaps in the new &lt;code&gt;[expo-file-system](https://docs.expo.dev/versions/v56.0.0/sdk/filesystem/)&lt;/code&gt; API that became the default in SDK 54. &lt;code&gt;File.downloadFileAsync()&lt;/code&gt; now reports progress and supports &lt;code&gt;AbortSignal&lt;/code&gt;, and copy/move operations accept an &lt;code&gt;overwrite&lt;/code&gt; option.&lt;/p&gt;

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

&lt;p&gt;File picking is more capable now too: &lt;code&gt;File.pickFileAsync()&lt;/code&gt; supports selecting multiple files and multiple MIME types, bringing it closer to &lt;code&gt;[expo-document-picker](https://docs.expo.dev/versions/v56.0.0/sdk/document-picker/)&lt;/code&gt; feature parity. We also fixed several correctness and reliability issues, including large-file &lt;code&gt;md5&lt;/code&gt; hashing memory usage, Android SAF copy/move support, and &lt;code&gt;totalDiskSpace&lt;/code&gt; reporting on iOS.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  &lt;strong&gt;Status bar and navigation bar APIs are now consistent&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Both &lt;code&gt;[expo-status-bar](https://docs.expo.dev/versions/v56.0.0/sdk/status-bar/)&lt;/code&gt; and &lt;code&gt;[expo-navigation-bar](https://docs.expo.dev/versions/v56.0.0/sdk/navigation-bar/)&lt;/code&gt; 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 &lt;code&gt;&amp;lt;NavigationBar&amp;gt;&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StatusBar&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expo-status-bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NavigationBar&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expo-navigation-bar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Imperative API&lt;/span&gt;
    &lt;span class="nx"&gt;StatusBar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;StatusBar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;NavigationBar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;NavigationBar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Declarative API: equivalent to the imperative calls above, with multiple instances merging in mount order */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StatusBar&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"auto"&lt;/span&gt; &lt;span class="na"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NavigationBar&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"auto"&lt;/span&gt; &lt;span class="na"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also added a config plugin for &lt;code&gt;expo-status-bar&lt;/code&gt;, and both packages' plugin options now align:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;app.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"expo-status-bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"light"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hidden"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"expo-navigation-bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"light"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hidden"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;New Calendar, Contacts, and MediaLibrary APIs are now stable&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;With the release of Expo SDK 56, the next versions of the &lt;code&gt;[expo-calendar](https://docs.expo.dev/versions/v56.0.0/sdk/calendar/)&lt;/code&gt;, &lt;code&gt;[expo-media-library](https://docs.expo.dev/versions/v56.0.0/sdk/media-library/)&lt;/code&gt;, and &lt;code&gt;[expo-contacts](https://docs.expo.dev/versions/v56.0.0/sdk/contacts/)&lt;/code&gt; libraries are officially promoted to stable.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;For more technical details and usage examples on the new MediaLibrary and Contacts APIs, check out &lt;a href="https://expo.dev/blog/the-next-generation-of-expo-apis-medialibrary-and-contacts" rel="noopener noreferrer"&gt;the blog post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Widgets for iOS promoted to stable&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;After introducing an alpha version of &lt;a href="https://docs.expo.dev/versions/v56.0.0/sdk/widgets/" rel="noopener noreferrer"&gt;Expo Widgets&lt;/a&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;AI-friendly project scaffolding&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Agent-ready scaffolding&lt;/strong&gt;: new projects include &lt;strong&gt;AGENTS.md&lt;/strong&gt;, &lt;strong&gt;CLAUDE.md&lt;/strong&gt;, and &lt;code&gt;.claude/settings.json&lt;/code&gt; with Expo-specific guidance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Official &lt;strong&gt;[&lt;/strong&gt;Expo Skills**](&lt;a href="https://docs.expo.dev/skills/" rel="noopener noreferrer"&gt;https://docs.expo.dev/skills/&lt;/a&gt;)&lt;/strong&gt; for AI agents**: install in Claude Code with &lt;code&gt;/plugin marketplace add expo/skills&lt;/code&gt; followed by &lt;code&gt;/plugin install expo&lt;/code&gt;. For Codex, Cursor, or any other agent, run &lt;code&gt;npx skills add expo/skills&lt;/code&gt;. See the docs for per-tool setup details.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Convex integration&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;EAS now provisions and links &lt;a href="https://www.convex.dev/" rel="noopener noreferrer"&gt;Convex&lt;/a&gt; backends for you. Run &lt;code&gt;eas integrations:convex:connect&lt;/code&gt; in your project and we'll install &lt;code&gt;convex&lt;/code&gt;, create (or reuse) a Convex team linked to your EAS account, set up a project with a dev deployment, and write &lt;code&gt;CONVEX_DEPLOY_KEY&lt;/code&gt; and &lt;code&gt;EXPO_PUBLIC_CONVEX_URL&lt;/code&gt; to your &lt;strong&gt;.env.local&lt;/strong&gt;. We also create &lt;code&gt;EXPO_PUBLIC_CONVEX_URL&lt;/code&gt; as an EAS environment variable across Production, Preview, and Development so EAS Build picks it up automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Expo CLI&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgfqlkuuk4jc5jjzpsfyo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgfqlkuuk4jc5jjzpsfyo.png" alt="expo start is 5x faster, Metro crawl 6x faster, dev memory âˆ’28 %, cold bundling 20-50%, warm bundling 3-8x faster" width="800" height="649"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SDK 56 ships the first wave of performance improvements across the whole bundling and run pipeline, with more landing in future releases.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Faster CLI&lt;/strong&gt;: Various performance metrics of the Expo CLI have been improved in SDK 56&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;On-demand Filesystem&lt;/strong&gt;: eliminates &lt;code&gt;watchFolders&lt;/code&gt; as a load-bearing configuration option. Enabled by default; disable it by adding &lt;code&gt;experiment.onDemandFilesystem: false&lt;/code&gt; to your &lt;strong&gt;app.json&lt;/strong&gt;. &lt;a href="https://github.com/expo/expo/pull/45391" rel="noopener noreferrer"&gt;Learn more in the PR&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Native Node.js watcher by default&lt;/strong&gt;: rather than Watchman, we now use a native Node.js watcher and crawler by default. You can switch back to Watchman with &lt;code&gt;resolver.useWatchman&lt;/code&gt; in a Metro config, but it's no longer recommended.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;TypeScript 6 support and TypeScript 7 readiness&lt;/strong&gt;: we replaced our &lt;a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-6-0.html" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt; resolution to support TS 6 and prepare for &lt;a href="https://devblogs.microsoft.com/typescript/announcing-typescript-7-0-beta/" rel="noopener noreferrer"&gt;TS 7&lt;/a&gt;. This resolves some monorepo bugs with &lt;code&gt;tsconfig.json&lt;/code&gt;'s &lt;code&gt;paths&lt;/code&gt; config (&lt;a href="https://github.com/expo/expo/pull/44791" rel="noopener noreferrer"&gt;#44791&lt;/a&gt;, &lt;a href="https://github.com/expo/expo/pull/45227" rel="noopener noreferrer"&gt;#45227&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;**import.meta**&lt;/code&gt;** support**: now enabled automatically (&lt;a href="https://github.com/expo/expo/pull/44239" rel="noopener noreferrer"&gt;#44239&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hermes v1 transforms&lt;/strong&gt;: fewer bundler transforms are enabled for Hermes, which reduces bundling times overall (&lt;a href="https://github.com/expo/expo/pull/45263" rel="noopener noreferrer"&gt;#45263&lt;/a&gt;, &lt;a href="https://github.com/expo/expo/pull/45345" rel="noopener noreferrer"&gt;#45345&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the On-demand Filesystem, you can also now try Expo with &lt;a href="https://pnpm.io/global-virtual-store" rel="noopener noreferrer"&gt;global virtual stores&lt;/a&gt; (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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Type-safe config plugins
&lt;/h3&gt;

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

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3hxhfq2k4c9n93ctisg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3hxhfq2k4c9n93ctisg.png" alt="JSDoc for an Expo config plugin's options inside app.config.ts" width="800" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;.ts&lt;/code&gt; files in your &lt;strong&gt;plugins&lt;/strong&gt; list, or write config plugins with &lt;code&gt;.mjs&lt;/code&gt; or &lt;code&gt;.cjs&lt;/code&gt; extensions.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Expo Router&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Expo goes way back with React Navigation: &lt;a href="https://github.com/brentvatne" rel="noopener noreferrer"&gt;@brentvatne&lt;/a&gt; led the project for the 1.0 and 2.0 releases, working together with &lt;a href="https://github.com/ericvicenti" rel="noopener noreferrer"&gt;@ericvicenti&lt;/a&gt; and &lt;a href="https://github.com/satya164" rel="noopener noreferrer"&gt;@satya164&lt;/a&gt;, and we helped to grow it from an idea, to a conceptual merging of React Native's old &lt;code&gt;NavigationExperimental&lt;/code&gt; and &lt;a href="https://github.com/expo/ex-navigation" rel="noopener noreferrer"&gt;ex-navigation&lt;/a&gt;, to the standard navigation library in the React Native ecosystem (now one of the standards, alongside Expo Router).&lt;/p&gt;

&lt;p&gt;Today, React Navigation is in &lt;a href="https://github.com/satya164" rel="noopener noreferrer"&gt;Satya's&lt;/a&gt; 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: &lt;code&gt;npx create-expo-app@latest --template react-navigation/template&lt;/code&gt;). Both libraries build upon &lt;a href="https://github.com/software-mansion/react-native-screens" rel="noopener noreferrer"&gt;react-native-screens&lt;/a&gt; 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.&lt;/p&gt;

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

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

&lt;p&gt;See the &lt;a href="https://docs.expo.dev/router/migrate/sdk-55-to-56/" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt; for full details, including manual migration steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  New features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;On Android, we've added experimental support for a toolbar. You can try it using the same API available on iOS: &lt;code&gt;[Stack.Toolbar](https://docs.expo.dev/router/advanced/stack-toolbar/)&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In collaboration with &lt;code&gt;[react-native-screens](https://github.com/software-mansion/react-native-screens)&lt;/code&gt;, we've also introduced experimental support for &lt;a href="https://github.com/software-mansion/react-native-screens/releases/tag/4.25.0-beta.1" rel="noopener noreferrer"&gt;a new version of the native stack&lt;/a&gt; (Stack v5), including initial support for Material-style headers and predictive back gesture.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For Expo on the Web, we now support streaming SSR when using the &lt;code&gt;unstable_useServerRendering&lt;/code&gt; flag. As part of this change, we've also introduced a new &lt;code&gt;[generateMetadata](https://docs.expo.dev/router/web/server-rendering/#metadata)&lt;/code&gt; function for retrieving and setting metadata on initial page load. The existing &lt;code&gt;&amp;lt;Head&amp;gt;&lt;/code&gt; component can still be used for updating metadata after hydration. Let us know how it works for you!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We've also added two new helpers for data loaders: &lt;code&gt;[createStaticLoader](https://docs.expo.dev/versions/v56.0.0/sdk/server/#createstaticloaderfn)&lt;/code&gt; and &lt;code&gt;[createServerLoader](https://docs.expo.dev/versions/v56.0.0/sdk/server/#createserverloaderfn)&lt;/code&gt;, which narrow the callback signature for each rendering mode. &lt;code&gt;createStaticLoader&lt;/code&gt; receives only route params (no &lt;code&gt;request&lt;/code&gt;), while &lt;code&gt;createServerLoader&lt;/code&gt; always passes a &lt;code&gt;request&lt;/code&gt; and throws an actionable error if mistakenly used during static generation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We've also added the ability to &lt;a href="https://docs.expo.dev/router/error-handling/#loading-states-with-suspense-fallback" rel="noopener noreferrer"&gt;customize the default &lt;/a&gt;&lt;code&gt;[&amp;lt;Suspense&amp;gt;](https://docs.expo.dev/router/error-handling/#loading-states-with-suspense-fallback)&lt;/code&gt;&lt;a href="https://docs.expo.dev/router/error-handling/#loading-states-with-suspense-fallback" rel="noopener noreferrer"&gt; fallbacks in a &lt;/a&gt;&lt;code&gt;[_layout](https://docs.expo.dev/router/error-handling/#loading-states-with-suspense-fallback)&lt;/code&gt;&lt;a href="https://docs.expo.dev/router/error-handling/#loading-states-with-suspense-fallback" rel="noopener noreferrer"&gt; route&lt;/a&gt;, giving you more control over loading states across your app. Following the same convention as &lt;code&gt;ErrorBoundary&lt;/code&gt;, you can export a &lt;code&gt;SuspenseFallback&lt;/code&gt; that receives route parameters, making it easy to show loading UI:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ActivityIndicator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;View&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expo-router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SuspenseFallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;alignItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ActivityIndicator&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"large"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RootLayout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Brownfield: more flexibility for embedded Expo apps&lt;/strong&gt;
&lt;/h2&gt;

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

&lt;p&gt;&lt;strong&gt;Multiple isolated apps in one host.&lt;/strong&gt; A new experimental option lets one host app contain multiple inner &lt;code&gt;[expo-brownfield](https://docs.expo.dev/versions/v56.0.0/sdk/brownfield/)&lt;/code&gt; apps. Opt in by setting &lt;code&gt;multipleFrameworks: true&lt;/code&gt; 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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;app.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"expo-brownfield"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ios"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"multipleFrameworks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Custom Turbo Modules from the host app.&lt;/strong&gt; Host apps can now register their own turbo module classes with the inner Expo app's React Native runtime, by passing a &lt;code&gt;turboModuleClasses&lt;/code&gt; dictionary into &lt;code&gt;ReactNativeHostManager.initialize&lt;/code&gt;. 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iOS prebuilds by default.&lt;/strong&gt; &lt;code&gt;expo-brownfield&lt;/code&gt; 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 &lt;code&gt;buildReactNativeFromSource&lt;/code&gt; plugin option.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Expo Application Services (EAS)&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Build time statistics for xcodebuild and Gradle
&lt;/h3&gt;

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

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvqb64b4dlszaqssqul75.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvqb64b4dlszaqssqul75.png" alt="EAS Build dashboard showing per-module Xcode compile metrics on the left and a Gradle task execution profile on the right" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prebuilt artifacts for major community libraries
&lt;/h3&gt;

&lt;p&gt;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 &lt;code&gt;react-native-reanimated&lt;/code&gt; and &lt;code&gt;react-native-screens&lt;/code&gt;. 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coming soon: EAS Observe
&lt;/h3&gt;

&lt;p&gt;We're working on &lt;a href="https://expo.dev/solutions/expo-observe" rel="noopener noreferrer"&gt;EAS Observe&lt;/a&gt;, 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) when something looks off. &lt;a href="https://github.com/kadikraman" rel="noopener noreferrer"&gt;Kadi Kraman&lt;/a&gt; shared a deeper preview at &lt;a href="https://appjs.co/" rel="noopener noreferrer"&gt;App.js Conf&lt;/a&gt; â€” keep an eye out for the recording.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Other notable changes&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;[**expo-audio**](https://docs.expo.dev/versions/v56.0.0/sdk/audio/)&lt;/code&gt;: new &lt;code&gt;useAudioStream&lt;/code&gt; hook for real-time microphone buffer access (&lt;a href="https://github.com/expo/expo/pull/44902" rel="noopener noreferrer"&gt;#44902&lt;/a&gt;). Live-stream improvements: &lt;code&gt;isLiveStream&lt;/code&gt; lock-screen option on iOS (&lt;a href="https://github.com/expo/expo/pull/43088" rel="noopener noreferrer"&gt;#43088&lt;/a&gt;), &lt;code&gt;playsInSilentMode&lt;/code&gt; on Android (&lt;a href="https://github.com/expo/expo/pull/43117" rel="noopener noreferrer"&gt;#43117&lt;/a&gt;), and &lt;code&gt;isLive&lt;/code&gt; / &lt;code&gt;currentOffsetFromLive&lt;/code&gt; / &lt;code&gt;error&lt;/code&gt; fields on &lt;code&gt;AudioStatus&lt;/code&gt; (&lt;a href="https://github.com/expo/expo/pull/44441" rel="noopener noreferrer"&gt;#44441&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;[**expo-haptics**](https://docs.expo.dev/versions/v56.0.0/sdk/haptics/)&lt;/code&gt;: web haptics on Safari (&lt;a href="https://github.com/expo/expo/pull/44261" rel="noopener noreferrer"&gt;#44261&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;[**expo-asset**](https://docs.expo.dev/versions/v56.0.0/sdk/asset/)&lt;/code&gt;: GLB model assets for 3D / AR work (&lt;a href="https://github.com/expo/expo/pull/42495" rel="noopener noreferrer"&gt;#42495&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;[**expo-sqlite**](https://docs.expo.dev/versions/v56.0.0/sdk/sqlite/)&lt;/code&gt;: native &lt;code&gt;ArrayBuffer&lt;/code&gt; for blob columns (&lt;a href="https://github.com/expo/expo/pull/42640" rel="noopener noreferrer"&gt;Android #42640&lt;/a&gt; / &lt;a href="https://github.com/expo/expo/pull/42642" rel="noopener noreferrer"&gt;iOS #42642&lt;/a&gt;), statement bind params (&lt;a href="https://github.com/expo/expo/pull/42639" rel="noopener noreferrer"&gt;#42639&lt;/a&gt;), and session changesets (&lt;a href="https://github.com/expo/expo/pull/42638" rel="noopener noreferrer"&gt;#42638&lt;/a&gt;) â€” replacing the legacy &lt;code&gt;JavaScriptArrayBuffer&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;[**expo-dev-launcher**](https://docs.expo.dev/versions/v56.0.0/sdk/dev-client/)&lt;/code&gt;: error-screen "Copy" button (&lt;a href="https://github.com/expo/expo/pull/44723" rel="noopener noreferrer"&gt;#44723&lt;/a&gt;), NDS service discovery on Android, Android edge-to-edge (&lt;a href="https://github.com/expo/expo/pull/44529" rel="noopener noreferrer"&gt;#44529&lt;/a&gt;), and plugin options for &lt;code&gt;defaultLaunchURL&lt;/code&gt; (&lt;a href="https://github.com/expo/expo/pull/44419" rel="noopener noreferrer"&gt;#44419&lt;/a&gt;), &lt;code&gt;skipOnboarding&lt;/code&gt;, and &lt;code&gt;showMenuAtLaunch&lt;/code&gt; (&lt;a href="https://github.com/expo/expo/pull/45167" rel="noopener noreferrer"&gt;#45167&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;**expo-doctor**&lt;/code&gt;: a new check warns when &lt;code&gt;expo-router&lt;/code&gt; and &lt;code&gt;react-navigation&lt;/code&gt; are both installed â€” a likely unintended pairing now that &lt;code&gt;expo-router&lt;/code&gt; no longer sits on top of &lt;code&gt;react-navigation&lt;/code&gt; (&lt;a href="https://github.com/expo/expo/pull/45323" rel="noopener noreferrer"&gt;#45323&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;[**expo/fetch**](https://docs.expo.dev/versions/v56.0.0/sdk/expo/#expofetch-api)&lt;/code&gt;: brotli, gzip, and zstd response decompression on Android (&lt;a href="https://github.com/expo/expo/pull/45458" rel="noopener noreferrer"&gt;#45458&lt;/a&gt;), and &lt;code&gt;AbortSignal.timeout&lt;/code&gt; / &lt;code&gt;AbortSignal.any&lt;/code&gt; support for WinterTC-compatible fetch behavior (&lt;a href="https://github.com/expo/expo/pull/45441" rel="noopener noreferrer"&gt;#45441&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Deprecations&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;**@expo/vector-icons**&lt;/code&gt;** will be replaced by &lt;strong&gt;`&lt;/strong&gt;@react-native-vector-icons/**&lt;em&gt;&lt;code&gt;: the new [scoped packages](https://github.com/oblador/react-native-vector-icons) (one per icon set, e.g. &lt;/code&gt;@react-native-vector-icons/material-design-icons&lt;code&gt;) are distinct from the older umbrella &lt;/code&gt;react-native-vector-icons&lt;code&gt; package that &lt;/code&gt;@expo/vector-icons&lt;code&gt; originally replaced. &lt;/code&gt;@expo/vector-icons&lt;code&gt; was created for Expo Go compatibility; recent upstream work has made that wrapper unnecessary, so consolidating onto &lt;/code&gt;@react-native-vector-icons/&lt;/em&gt;&lt;code&gt; reduces duplication and gives you the latest icons and fixes directly. Migrate today by running &lt;/code&gt;npx @react-native-vector-icons/codemod` â€” &lt;a href="https://github.com/oblador/react-native-vector-icons/blob/master/MIGRATION.md" rel="noopener noreferrer"&gt;Learn more about the codemod&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Original **`&lt;/strong&gt;expo-calendar*&lt;em&gt;`&lt;/em&gt;&lt;em&gt;, *&lt;/em&gt;&lt;code&gt;**expo-contacts**&lt;/code&gt;&lt;strong&gt;, and **`&lt;/strong&gt;expo-media-library*&lt;em&gt;`&lt;/em&gt;* APIs**: superseded by the redesigned versions now promoted to stable â€” the original APIs are deprecated.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Notable breaking changes&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;**expo/fetch**&lt;/code&gt;** as &lt;strong&gt;`&lt;/strong&gt;globalThis.fetch*&lt;em&gt;&lt;code&gt;: &lt;/code&gt;expo/fetch&lt;code&gt; is now installed as the default implementation of &lt;/code&gt;globalThis.fetch&lt;code&gt;, providing a WinterTC-compliant API and improved performance. Manual imports are no longer required. To opt out, set &lt;/code&gt;EXPO_PUBLIC_USE_RN_FETCH=1` in your *&lt;/em&gt;.env** file. &lt;a href="https://docs.expo.dev/versions/v56.0.0/sdk/expo/#expofetch-api" rel="noopener noreferrer"&gt;Learn more&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Async **`&lt;/strong&gt;copy()&lt;strong&gt;`&lt;/strong&gt; and &lt;strong&gt;`&lt;/strong&gt;move()&lt;strong&gt;`&lt;/strong&gt; in &lt;strong&gt;`&lt;/strong&gt;expo-file-system**&lt;code&gt;: these methods on &lt;/code&gt;File&lt;code&gt; and &lt;/code&gt;Directory&lt;code&gt; are now asynchronous and return a Promise. Use &lt;/code&gt;copySync()&lt;code&gt; and &lt;/code&gt;moveSync()` for synchronous behavior.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;**@expo/dom-webview**&lt;/code&gt;** as the default WebView for DOM components**: you no longer need the &lt;code&gt;react-native-webview&lt;/code&gt; dependency to use DOM components. You can still opt out and continue using &lt;code&gt;react-native-webview&lt;/code&gt; if needed. &lt;a href="https://docs.expo.dev/guides/dom-components/#usage" rel="noopener noreferrer"&gt;Learn more in the usage guide&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Related to the planned deprecation of &lt;code&gt;@expo/vector-icons&lt;/code&gt; (see the previous paragraph), the &lt;code&gt;expo&lt;/code&gt; package no longer depends on &lt;code&gt;@expo/vector-icons&lt;/code&gt;. If you wish to continue using &lt;code&gt;@expo/vector-icons&lt;/code&gt;, you need to explicitly add it to your project's dependencies in &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Tool version bumps&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Minimum Xcode bumped to 26.4.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Minimum iOS / tvOS bumped to 16.4, macOS to 13.4.&lt;/strong&gt; Up from iOS 15.1 (last bumped August 2024). Drops support for iPhone 7/7+, iPhone 6s/6s+, iPhone SE (1st gen), iPad mini 4, and iPad Air 2.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-6-0.html" rel="noopener noreferrer"&gt;TypeScript bumped to 6.0.3&lt;/a&gt;&lt;/strong&gt; â€” included in new project templates and pulled in for existing projects via &lt;code&gt;npx expo install --fix&lt;/code&gt;. To opt out, add &lt;code&gt;typescript&lt;/code&gt; to the &lt;code&gt;[expo.install.exclude](https://docs.expo.dev/versions/v56.0.0/config/package-json/#installexclude)&lt;/code&gt; field in your &lt;strong&gt;package.json&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Expo Go&lt;/strong&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Reminder&lt;/strong&gt;: 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 &lt;a href="https://docs.expo.dev/develop/development-builds/expo-go-to-dev-build/" rel="noopener noreferrer"&gt;development build&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Expo Go for SDK 56 is not available on the Apple App Store or Google Play Store.&lt;/strong&gt; We do not have a timeline for when they will be, and we'll update this post when we have more news to share.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You can install Expo Go for SDK 56 from Expo CLI directly on Android devices.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For iOS, you can use the &lt;a href="https://testflight.apple.com/join/GZJxxfUU" rel="noopener noreferrer"&gt;TestFlight External Beta&lt;/a&gt; or the &lt;code&gt;eas go&lt;/code&gt; command to create an Expo Go build for SDK 56 and upload it to your own TestFlight team.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Upgrading your app&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Try using our &lt;a href="https://github.com/expo/skills/tree/main/plugins/upgrading-expo" rel="noopener noreferrer"&gt;upgrade skills&lt;/a&gt; (&lt;a href="https://github.com/expo/skills/tree/main?tab=readme-ov-file#claude-code" rel="noopener noreferrer"&gt;installation instructions&lt;/a&gt;) with Claude Code, or a similar tool of your choice, to upgrade your app.&lt;/p&gt;

&lt;p&gt;Here's how to upgrade your app to Expo SDK 56 from 55:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Upgrade all dependencies to match SDK 56&lt;/strong&gt;:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check for any possible known issues with Expo Doctor&lt;/strong&gt;:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Refer to the &lt;strong&gt;[&lt;/strong&gt;"Deprecations"**](#deprecations)&lt;/strong&gt; and &lt;strong&gt;[&lt;/strong&gt;"Notable breaking changes"&lt;strong&gt;](#notable-breaking-changes)&lt;/strong&gt; sections** above for breaking changes that are most likely to impact your app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Make sure to check the &lt;strong&gt;[&lt;/strong&gt;changelog**](&lt;a href="https://github.com/expo/expo/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;https://github.com/expo/expo/blob/main/CHANGELOG.md&lt;/a&gt;)&lt;/strong&gt; for all other breaking changes!**&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Upgrade Xcode if needed&lt;/strong&gt;: Xcode 26.4 is required to compile a native iOS project. For EAS Build and Workflows, profiles without any specified &lt;code&gt;image&lt;/code&gt; will default to Xcode 26.4.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;iOS deployment target bump&lt;/strong&gt;: if you have any Expo modules of your own, update the iOS deployment target to &lt;code&gt;16.4&lt;/code&gt; in your podspec.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- s.platforms = { :ios =&amp;gt; '15.1' }
&lt;/span&gt;&lt;span class="gi"&gt;+ s.platforms = { :ios =&amp;gt; '16.4' }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;*&lt;em&gt;If you use *&lt;/em&gt;&lt;a href="https://docs.expo.dev/workflow/continuous-native-generation/" rel="noopener noreferrer"&gt;&lt;strong&gt;Continuous Native Generation&lt;/strong&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete the &lt;strong&gt;android&lt;/strong&gt; and &lt;strong&gt;ios&lt;/strong&gt; directories if you generated them for a previous SDK version in your local project directory. They'll be re-generated next time you run a build, either with &lt;code&gt;npx expo run:ios&lt;/code&gt;, &lt;code&gt;npx expo prebuild&lt;/code&gt;, or with EAS Build.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;If you **&lt;u&gt;&lt;/u&gt;&lt;/strong&gt;don't*&lt;em&gt;&lt;/em&gt;* use &lt;strong&gt;[&lt;/strong&gt;Continuous Native Generation**](&lt;a href="https://docs.expo.dev/workflow/continuous-native-generation/):" rel="noopener noreferrer"&gt;https://docs.expo.dev/workflow/continuous-native-generation/):&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;npx pod-install&lt;/code&gt; if you have an &lt;code&gt;ios&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;Apply any relevant changes from the &lt;a href="https://docs.expo.dev/bare/upgrade/?fromSdk=55&amp;amp;toSdk=56" rel="noopener noreferrer"&gt;Native project upgrade helper&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Optionally, you could consider &lt;a href="https://docs.expo.dev/guides/adopting-prebuild/" rel="noopener noreferrer"&gt;adopting prebuild&lt;/a&gt; for easier upgrades in the future.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;*&lt;em&gt;If you use *&lt;/em&gt;&lt;a href="https://docs.expo.dev/develop/development-builds/introduction/" rel="noopener noreferrer"&gt;&lt;strong&gt;development builds with expo-dev-client&lt;/strong&gt;&lt;/a&gt;: Create a new development build after upgrading.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;If you use Expo Go&lt;/strong&gt;: consider migrating to &lt;a href="https://docs.expo.dev/develop/development-builds/expo-go-to-dev-build/" rel="noopener noreferrer"&gt;development builds&lt;/a&gt;. &lt;a href="https://expo.fyi/expo-go-usage" rel="noopener noreferrer"&gt;Expo Go is not recommended as a development environment for production apps&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Having trouble?&lt;/strong&gt; Refer to the &lt;a href="https://expo.fyi/troubleshooting-sdk-upgrades" rel="noopener noreferrer"&gt;Troubleshooting your SDK upgrade&lt;/a&gt; guide.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Questions?&lt;/strong&gt; Join our weekly office hours on Wednesdays at 12:00PM Pacific &lt;a href="https://chat.expo.dev/" rel="noopener noreferrer"&gt;on Discord&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Thanks to everyone who contributed to the release!&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Thanks to &lt;a href="https://expo.dev/about" rel="noopener noreferrer"&gt;the Expo team&lt;/a&gt; â€” everyone contributed one way or another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;External contributors&lt;/strong&gt;: &lt;a href="https://github.com/abraj" rel="noopener noreferrer"&gt;Abhishek Raj&lt;/a&gt;, &lt;a href="https://github.com/aramikuto" rel="noopener noreferrer"&gt;Aleksandr Kondrashov&lt;/a&gt;, &lt;a href="https://github.com/alfonsocj" rel="noopener noreferrer"&gt;Alfonso Curbelo&lt;/a&gt;, &lt;a href="https://github.com/AmauryLiet" rel="noopener noreferrer"&gt;Amaury Liet&lt;/a&gt;, &lt;a href="https://github.com/andrejpavlovic" rel="noopener noreferrer"&gt;Andrej Pavlovic&lt;/a&gt;, &lt;a href="https://github.com/SialB" rel="noopener noreferrer"&gt;Arthur Blais&lt;/a&gt;, &lt;a href="https://github.com/artus9033" rel="noopener noreferrer"&gt;Artur Morys - Magiera&lt;/a&gt;, &lt;a href="https://github.com/AzeemIdrisi" rel="noopener noreferrer"&gt;Azeem Idrisi&lt;/a&gt;, &lt;a href="https://github.com/azro352" rel="noopener noreferrer"&gt;azro352&lt;/a&gt;, &lt;a href="https://github.com/benschac" rel="noopener noreferrer"&gt;benjamin&lt;/a&gt;, &lt;a href="https://github.com/benjaminkomen" rel="noopener noreferrer"&gt;Benjamin Komen&lt;/a&gt;, &lt;a href="https://github.com/bwallberg" rel="noopener noreferrer"&gt;Benjamin Wallberg&lt;/a&gt;, &lt;a href="https://github.com/blazejkustra" rel="noopener noreferrer"&gt;BÅ‚aÅ¼ej Kustra&lt;/a&gt;, &lt;a href="https://github.com/CalvinNFT" rel="noopener noreferrer"&gt;C. Obama&lt;/a&gt;, &lt;a href="https://github.com/CamWass" rel="noopener noreferrer"&gt;CamWass&lt;/a&gt;, &lt;a href="https://github.com/Choco-milk-for-u" rel="noopener noreferrer"&gt;Choco&lt;/a&gt;, &lt;a href="https://github.com/chrism" rel="noopener noreferrer"&gt;Chris Masters&lt;/a&gt;, &lt;a href="https://github.com/chriszs" rel="noopener noreferrer"&gt;Chris Zubak-Skees&lt;/a&gt;, &lt;a href="https://github.com/cwooldridge1" rel="noopener noreferrer"&gt;Christian Wooldridge&lt;/a&gt;, &lt;a href="https://github.com/cibucristi" rel="noopener noreferrer"&gt;Cristian C.&lt;/a&gt;, &lt;a href="https://github.com/daliboru" rel="noopener noreferrer"&gt;Dalibor Belic&lt;/a&gt;, &lt;a href="https://github.com/pubkey" rel="noopener noreferrer"&gt;Daniel Meyer&lt;/a&gt;, &lt;a href="https://github.com/reichhartd" rel="noopener noreferrer"&gt;Daniel Reichhart&lt;/a&gt;, &lt;a href="https://github.com/danishshaik" rel="noopener noreferrer"&gt;Danish&lt;/a&gt;, &lt;a href="https://github.com/DaveyEke" rel="noopener noreferrer"&gt;Dave Mkpa-Eke&lt;/a&gt;, &lt;a href="https://github.com/delphinebugner" rel="noopener noreferrer"&gt;Delphine Bugner&lt;/a&gt;, &lt;a href="https://github.com/morellodev" rel="noopener noreferrer"&gt;Dennis Morello&lt;/a&gt;, &lt;a href="https://github.com/desii101" rel="noopener noreferrer"&gt;desii&lt;/a&gt;, &lt;a href="https://github.com/DimitarNestorov" rel="noopener noreferrer"&gt;Dimitar Nestorov&lt;/a&gt;, &lt;a href="https://github.com/dogukany" rel="noopener noreferrer"&gt;DoÄŸukan YÄ±ldÄ±z&lt;/a&gt;, &lt;a href="https://github.com/codenamenam" rel="noopener noreferrer"&gt;Donghoon Nam&lt;/a&gt;, &lt;a href="https://github.com/dwightwatson" rel="noopener noreferrer"&gt;Dwight Watson&lt;/a&gt;, &lt;a href="https://github.com/dylancom" rel="noopener noreferrer"&gt;Dylan&lt;/a&gt;, &lt;a href="https://github.com/emillinden" rel="noopener noreferrer"&gt;Emil LindÃ©n&lt;/a&gt;, &lt;a href="https://github.com/erickreutz" rel="noopener noreferrer"&gt;Eric Kreutzer&lt;/a&gt;, &lt;a href="https://github.com/EricZeiberg" rel="noopener noreferrer"&gt;Eric Zeiberg&lt;/a&gt;, &lt;a href="https://github.com/nandorojo" rel="noopener noreferrer"&gt;Fernando Rojo&lt;/a&gt;, &lt;a href="https://github.com/frankcalise" rel="noopener noreferrer"&gt;Frank Calise&lt;/a&gt;, &lt;a href="https://github.com/garygcchiu" rel="noopener noreferrer"&gt;Gary Chiu&lt;/a&gt;, &lt;a href="https://github.com/hryhoriiK97" rel="noopener noreferrer"&gt;Gregory Moskaliuk&lt;/a&gt;, &lt;a href="https://github.com/gustavoharff" rel="noopener noreferrer"&gt;Gustavo Harff&lt;/a&gt;, &lt;a href="https://github.com/huextrat" rel="noopener noreferrer"&gt;Hugo Extrat&lt;/a&gt;, &lt;a href="https://github.com/ink404" rel="noopener noreferrer"&gt;Ian K&lt;/a&gt;, &lt;a href="https://github.com/Isaiah-Hamilton" rel="noopener noreferrer"&gt;Isaiah Hamilton&lt;/a&gt;, &lt;a href="https://github.com/kosmydel" rel="noopener noreferrer"&gt;Jakub Kosmydel&lt;/a&gt;, &lt;a href="https://github.com/Jc-Cloete" rel="noopener noreferrer"&gt;Jc Cloete&lt;/a&gt;, &lt;a href="https://github.com/jerone" rel="noopener noreferrer"&gt;Jeroen van Warmerdam&lt;/a&gt;, &lt;a href="https://github.com/Jeroen-G" rel="noopener noreferrer"&gt;JeroenG&lt;/a&gt;, &lt;a href="https://github.com/jeppester" rel="noopener noreferrer"&gt;Jesper SÃ¸rensen&lt;/a&gt;, &lt;a href="https://github.com/ushuz" rel="noopener noreferrer"&gt;John HU&lt;/a&gt;, &lt;a href="https://github.com/jbaudanza" rel="noopener noreferrer"&gt;Jonathan Baudanza&lt;/a&gt;, &lt;a href="https://github.com/jonemilnik" rel="noopener noreferrer"&gt;Jonathan Rivera&lt;/a&gt;, &lt;a href="https://github.com/jgmagift" rel="noopener noreferrer"&gt;Joseph Gift&lt;/a&gt;, &lt;a href="https://github.com/juliesaia" rel="noopener noreferrer"&gt;Julie Saia&lt;/a&gt;, &lt;a href="https://github.com/jurajpaska8" rel="noopener noreferrer"&gt;jurajpaska8&lt;/a&gt;, &lt;a href="https://github.com/dileepapeiris" rel="noopener noreferrer"&gt;K.Dileepa Thushan Peiris&lt;/a&gt;, &lt;a href="https://github.com/kzhgit" rel="noopener noreferrer"&gt;Kazuho Maejima&lt;/a&gt;, &lt;a href="https://github.com/kfirfitousi" rel="noopener noreferrer"&gt;Kfir Fitousi&lt;/a&gt;, &lt;a href="https://github.com/kimchi-developer" rel="noopener noreferrer"&gt;kimchi-developer&lt;/a&gt;, &lt;a href="https://github.com/TheAmphibianX" rel="noopener noreferrer"&gt;Kornelijus Å&amp;nbsp;liubauskas&lt;/a&gt;, &lt;a href="https://github.com/kraenhansen" rel="noopener noreferrer"&gt;KrÃ¦n Hansen&lt;/a&gt;, &lt;a href="https://github.com/KrastanD" rel="noopener noreferrer"&gt;Krastan Dimitrov&lt;/a&gt;, &lt;a href="https://github.com/kyleledbetter" rel="noopener noreferrer"&gt;Kyle Ledbetter&lt;/a&gt;, &lt;a href="https://github.com/focux" rel="noopener noreferrer"&gt;Leonardo E. Dominguez&lt;/a&gt;, &lt;a href="https://github.com/leonmetthez" rel="noopener noreferrer"&gt;leonmetthez&lt;/a&gt;, &lt;a href="https://github.com/chollier" rel="noopener noreferrer"&gt;Loic CHOLLIER&lt;/a&gt;, &lt;a href="https://github.com/lucabc2000" rel="noopener noreferrer"&gt;lucabc2000&lt;/a&gt;, &lt;a href="https://github.com/lsarni" rel="noopener noreferrer"&gt;Lucia Sarni&lt;/a&gt;, &lt;a href="https://github.com/kamui545" rel="noopener noreferrer"&gt;Manu&lt;/a&gt;, &lt;a href="https://github.com/matinzd" rel="noopener noreferrer"&gt;Matin Zadeh Dolatabad&lt;/a&gt;, &lt;a href="https://github.com/shottah" rel="noopener noreferrer"&gt;Matthew Abraham&lt;/a&gt;, &lt;a href="https://github.com/bonjourmauko" rel="noopener noreferrer"&gt;Mauko Quiroga-Alvarado&lt;/a&gt;, &lt;a href="https://github.com/blancham" rel="noopener noreferrer"&gt;Maxime&lt;/a&gt;, &lt;a href="https://github.com/szydlovsky" rel="noopener noreferrer"&gt;MikoÅ‚aj SzydÅ‚owski&lt;/a&gt;, &lt;a href="https://github.com/mohammadamin16" rel="noopener noreferrer"&gt;Mohammad Amin&lt;/a&gt;, &lt;a href="https://github.com/mmomtchev" rel="noopener noreferrer"&gt;Momtchil Momtchev&lt;/a&gt;, &lt;a href="https://github.com/Miigaarino" rel="noopener noreferrer"&gt;Myagmarsuren&lt;/a&gt;, &lt;a href="https://github.com/pachun" rel="noopener noreferrer"&gt;Nicholas Pachulski&lt;/a&gt;, &lt;a href="https://github.com/nickater" rel="noopener noreferrer"&gt;Nick Ater&lt;/a&gt;, &lt;a href="https://github.com/cortinico" rel="noopener noreferrer"&gt;Nicola Corti&lt;/a&gt;, &lt;a href="https://github.com/OtavioStasiak" rel="noopener noreferrer"&gt;OtÃ¡vio Stasiak&lt;/a&gt;, &lt;a href="https://github.com/patrickmichalik" rel="noopener noreferrer"&gt;Patrick Michalik&lt;/a&gt;, &lt;a href="https://github.com/patw0929" rel="noopener noreferrer"&gt;Patrick Wang&lt;/a&gt;, &lt;a href="https://github.com/pmleczek" rel="noopener noreferrer"&gt;Patryk Mleczek&lt;/a&gt;, &lt;a href="https://github.com/peterlazar1993" rel="noopener noreferrer"&gt;Peter Lazar&lt;/a&gt;, &lt;a href="https://github.com/pchalupa" rel="noopener noreferrer"&gt;Petr Chalupa&lt;/a&gt;, &lt;a href="https://github.com/Pflaumenbaum" rel="noopener noreferrer"&gt;Pflaumenbaum&lt;/a&gt;, &lt;a href="https://github.com/preetpatel" rel="noopener noreferrer"&gt;Preet Patel&lt;/a&gt;, &lt;a href="https://github.com/Randall71" rel="noopener noreferrer"&gt;Randall71&lt;/a&gt;, &lt;a href="https://github.com/Regi24" rel="noopener noreferrer"&gt;Regi24&lt;/a&gt;, &lt;a href="https://github.com/tmdgusya" rel="noopener noreferrer"&gt;roach&lt;/a&gt;, &lt;a href="https://github.com/rodrigoaraujo7" rel="noopener noreferrer"&gt;Rodrigo Leite Araujo&lt;/a&gt;, &lt;a href="https://github.com/ronickg" rel="noopener noreferrer"&gt;Ronald Goedeke&lt;/a&gt;, &lt;a href="https://github.com/SamuelBrucksch" rel="noopener noreferrer"&gt;Samuel Brucksch&lt;/a&gt;, &lt;a href="https://github.com/mozzius" rel="noopener noreferrer"&gt;Samuel Newman&lt;/a&gt;, &lt;a href="https://github.com/satya164" rel="noopener noreferrer"&gt;Satyajit Sahoo&lt;/a&gt;, &lt;a href="https://github.com/sergical" rel="noopener noreferrer"&gt;Sergiy Dybskiy&lt;/a&gt;, &lt;a href="https://github.com/psnet" rel="noopener noreferrer"&gt;Serhii Pustovit&lt;/a&gt;, &lt;a href="https://github.com/smoores-dev" rel="noopener noreferrer"&gt;Shane Friedman&lt;/a&gt;, &lt;a href="https://github.com/Shoghy" rel="noopener noreferrer"&gt;Shoghy Martinez&lt;/a&gt;, &lt;a href="https://github.com/shubh73" rel="noopener noreferrer"&gt;Shubh Porwal&lt;/a&gt;, &lt;a href="https://github.com/shindeshubhamm" rel="noopener noreferrer"&gt;Shubham Shinde&lt;/a&gt;, &lt;a href="https://github.com/SnowingFox" rel="noopener noreferrer"&gt;snowingfox&lt;/a&gt;, &lt;a href="https://github.com/starsky-nev" rel="noopener noreferrer"&gt;starsky-nev&lt;/a&gt;, &lt;a href="https://github.com/teamclouday" rel="noopener noreferrer"&gt;teamclouday&lt;/a&gt;, &lt;a href="https://github.com/terijaki" rel="noopener noreferrer"&gt;Terijaki&lt;/a&gt;, &lt;a href="https://github.com/TheUntraceable" rel="noopener noreferrer"&gt;TheUntraceable&lt;/a&gt;, &lt;a href="https://github.com/tmallet" rel="noopener noreferrer"&gt;ThiMal&lt;/a&gt;, &lt;a href="https://github.com/tomekzaw" rel="noopener noreferrer"&gt;Tomasz Zawadzki&lt;/a&gt;, &lt;a href="https://github.com/tyrauber" rel="noopener noreferrer"&gt;Ty Rauber&lt;/a&gt;, &lt;a href="https://github.com/victor-bolivar" rel="noopener noreferrer"&gt;Victor Bolivar De la Cruz&lt;/a&gt;, &lt;a href="https://github.com/dwnste" rel="noopener noreferrer"&gt;Vsevolod Lomovitsky&lt;/a&gt;, &lt;a href="https://github.com/xoyseau" rel="noopener noreferrer"&gt;xoyseau&lt;/a&gt;, &lt;a href="https://github.com/yerevin" rel="noopener noreferrer"&gt;yerevin&lt;/a&gt;, and &lt;a href="https://github.com/doombladeoff" rel="noopener noreferrer"&gt;Zhovtonizhko Dmitriy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Beta testers&lt;/strong&gt;: &lt;a href="https://github.com/agrittiwari" rel="noopener noreferrer"&gt;Agrit Tiwari&lt;/a&gt;, &lt;a href="https://github.com/Amrit0991" rel="noopener noreferrer"&gt;Amrit Saini&lt;/a&gt;, &lt;a href="https://github.com/androidanimation" rel="noopener noreferrer"&gt;androidanimation&lt;/a&gt;, &lt;a href="https://github.com/sync" rel="noopener noreferrer"&gt;Anthony Mittaz&lt;/a&gt;, &lt;a href="https://github.com/berhanserin" rel="noopener noreferrer"&gt;Berhan&lt;/a&gt;, &lt;a href="https://github.com/branaust" rel="noopener noreferrer"&gt;Brandon Austin&lt;/a&gt;, &lt;a href="https://github.com/DavidJGrimsley" rel="noopener noreferrer"&gt;David Grimsley&lt;/a&gt;, &lt;a href="https://github.com/dylanfcsr" rel="noopener noreferrer"&gt;dylanfcsr&lt;/a&gt;, &lt;a href="https://github.com/eduardinni" rel="noopener noreferrer"&gt;Eduardo LomelÃ­&lt;/a&gt;, &lt;a href="https://github.com/tomoakikuroiwa" rel="noopener noreferrer"&gt;ifx326&lt;/a&gt;, &lt;a href="https://github.com/OkuraKenG" rel="noopener noreferrer"&gt;Kenji Okura&lt;/a&gt;, &lt;a href="https://github.com/Kingfapa" rel="noopener noreferrer"&gt;Kingfapa&lt;/a&gt;, &lt;a href="https://github.com/Luc1412" rel="noopener noreferrer"&gt;Lucas Hardt&lt;/a&gt;, &lt;a href="https://github.com/mhoran" rel="noopener noreferrer"&gt;Matthew Horan&lt;/a&gt;, &lt;a href="https://github.com/maxvaljan" rel="noopener noreferrer"&gt;Max&lt;/a&gt;, &lt;a href="https://github.com/robrechtme-itp" rel="noopener noreferrer"&gt;Robrecht Meersman&lt;/a&gt;, &lt;a href="https://github.com/rodperottoni" rel="noopener noreferrer"&gt;Rodolfo Perottoni&lt;/a&gt;, &lt;a href="https://github.com/simondaigre" rel="noopener noreferrer"&gt;Simon&lt;/a&gt;, and &lt;a href="https://github.com/H-Sven" rel="noopener noreferrer"&gt;Sven&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>expo</category>
      <category>reactnative</category>
      <category>mobile</category>
      <category>javascript</category>
    </item>
    <item>
      <title>ASO Skills: ASO in Your AI Workflow</title>
      <dc:creator>Dan</dc:creator>
      <pubDate>Tue, 19 May 2026 22:59:45 +0000</pubDate>
      <link>https://dev.to/expo/aso-skills-aso-in-your-ai-workflow-3288</link>
      <guid>https://dev.to/expo/aso-skills-aso-in-your-ai-workflow-3288</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://expo.dev/blog/aso-skills-aso-in-your-ai-workflow" rel="noopener noreferrer"&gt;expo.dev/blog&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;By Erencan Arica&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;A few years ago, launching a React Native app meant weeks of setup configuring Xcode, managing signing certificates, wrestling with Android Gradle configs, and cobbling together a CI/CD pipeline from scratch. Today, with Expo, a solo developer can go from idea to a production-ready native app in a single afternoon. It's a dream. And that's before we even mention LLMs.&lt;/p&gt;

&lt;p&gt;But speed creates a new problem: when shipping is fast, the bottleneck shifts. It's no longer &lt;em&gt;building&lt;/em&gt; the app, it's &lt;em&gt;getting it found&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That's where &lt;a href="https://en.wikipedia.org/wiki/App_store_optimization" rel="noopener noreferrer"&gt;App Store Optimization&lt;/a&gt; comes in.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why shipping fast made App Store discovery harder
&lt;/h2&gt;

&lt;p&gt;Here's the tension: Expo makes is easier for everyone to ship an app and now the App Store has over &lt;strong&gt;2 million apps&lt;/strong&gt; competing for attention. Paid acquisition costs have risen over 30% year-over-year, making organic discovery more important than ever.&lt;/p&gt;

&lt;p&gt;And here's the stat that changes everything: &lt;strong&gt;65–70% of app downloads still come from App Store search.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not ads. Not social media. Not influencers. &lt;strong&gt;Search&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your app doesn't rank for the right keywords, most of your potential users will never see it regardless of how polished the product is. Great apps with poor store listings get buried. Mediocre apps with excellent ASO get downloaded.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is App Store Optimization (ASO)?
&lt;/h2&gt;

&lt;p&gt;Before going deeper, I want to explain what App Store Optimization (ASO) actually means. ASO is the practice of optimizing your app to rank higher in App Store or Play Store search results tp attract more users to install.&lt;/p&gt;

&lt;p&gt;Think of it as SEO, but for the App Store. The principles are similar: understand what your users are searching for, then make sure your listing reflects it clearly and accurately.&lt;/p&gt;

&lt;p&gt;There are key elements of ASO:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Title (30 chars)&lt;/strong&gt;: Your strongest ranking signal. A core keyword in the title can lift rankings by up to 10%.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Subtitle (30 chars, iOS):&lt;/strong&gt; A secondary ranking signal. Complement your title with different keywords, both fields are indexed together.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keyword Field (100 chars, iOS):&lt;/strong&gt; Hidden from users, fully indexed by Apple. No spaces after commas, no duplicates from your title, no filler words.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Screenshots:&lt;/strong&gt; No longer just a conversion tool. With Apple's semantic search, the text in your screenshots is indexed and affects your ranking directly. Users decide in ~7 seconds, so lead with your value prop, use keyword-rich captions, and show real UI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ratings &amp;amp; Reviews:&lt;/strong&gt; A direct ranking factor. Prompt at the right moment, after a user achieves something, not on first launch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Localization:&lt;/strong&gt; Each locale gets its own keyword field, title, and screenshots. Most developers only do English and leave massive markets untapped.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ASO is not a one-time task. Algorithms evolve, competitors iterate, new keywords emerge. The best teams treat it as an ongoing practice, not a launch checklist. It’s best to revisit your listings each month. That’s why I came up with open-source ASO agent skills with real-time data.&lt;/p&gt;
&lt;h2&gt;
  
  
  ASO Skills: bringing App Store Optimization into your AI workflow
&lt;/h2&gt;

&lt;p&gt;This is the problem I wanted to solve when I built &lt;a href="https://github.com/Eronred/aso-skills" rel="noopener noreferrer"&gt;ASO Skills&lt;/a&gt;, an open-source collection of AI agent skills for App Store Optimization and mobile app marketing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5cz66cpgc1re6f3kzaw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5cz66cpgc1re6f3kzaw.jpg" alt="Appeeky MCP" width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The idea is simple: &lt;strong&gt;package ASO expertise directly into the tools you already use.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Whether you're working in Cursor, Claude Code, Codex or any Agent Skills-compatible environment, you can ask your AI assistant to run a full ASO audit, research keywords, analyze competitors, and generate optimized metadata, and get structured, actionable results backed by real App Store data.&lt;/p&gt;

&lt;p&gt;The library ships with &lt;strong&gt;20+ skills&lt;/strong&gt; across three areas. But what makes them powerful isn't the individual skills, it's how they work together. Each skill is aware of the others, so instead of running isolated commands, you get a natural, guided workflow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjr39l9mek23ya1yuiqqh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjr39l9mek23ya1yuiqqh.png" alt="ASO Flow" width="800" height="815"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aso-audit
   → flags weak spots in your listing
   → recommends keyword-research for gaps

keyword-research
   → finds high-opportunity keywords for your category
   → feeds into metadata-optimization

metadata-optimization
   → generates your title, subtitle, and keyword field
   → ready to ship
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You start with an audit, follow the recommendations, and end with a fully optimized listing, all in one conversation with your AI assistant. No dashboards, no spreadsheets, no context switching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build, ship, and optimize your app in one workflow
&lt;/h2&gt;

&lt;p&gt;The Expo development workflow is fast, automated, and increasingly AI-native. You can build a polished app, run tests, and ship to production without leaving your editor.&lt;/p&gt;

&lt;p&gt;ASO Skills brings the same philosophy to your marketing workflow. You shouldn't have to context-switch to a separate dashboard, copy-paste keywords into a spreadsheet, or hire a consultant to understand why your app isn't ranking.&lt;/p&gt;

&lt;p&gt;Ask your AI. Get a structured audit. Fix the highest-impact issues. Ship the update with Expo’s services.&lt;/p&gt;

&lt;p&gt;The entire loop - build, ship, optimize, iterate - now lives in one environment. Expo already removed the friction from shipping. I hope these skills remove the friction from being found.&lt;/p&gt;

&lt;p&gt;Here is the repo: &lt;a href="http://github.com/Eronred/aso-skills" rel="noopener noreferrer"&gt;github.com/Eronred/aso-skills&lt;/a&gt;&lt;/p&gt;

</description>
      <category>expo</category>
      <category>mobile</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to keep your OTA updates lean and fast</title>
      <dc:creator>Dan</dc:creator>
      <pubDate>Sun, 17 May 2026 16:32:20 +0000</pubDate>
      <link>https://dev.to/expo/how-to-keep-your-ota-updates-lean-and-fast-3afn</link>
      <guid>https://dev.to/expo/how-to-keep-your-ota-updates-lean-and-fast-3afn</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://expo.dev/blog/how-to-keep-your-ota-updates-lean-and-fast" rel="noopener noreferrer"&gt;expo.dev/blog&lt;/a&gt; by Jacob Clausen.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Have you ever shipped a critical fix to your entire user base within minutes of merging the PR - a straight highway from your fix to your users’ fingertips, no bureaucracy, no interruption to the user experience? That feeling never gets old.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.expo.dev/eas-update/introduction/" rel="noopener noreferrer"&gt;EAS Update&lt;/a&gt; makes this possible, and once you’ve experienced it, it’s hard to imagine working without it. But publishing the fix is only half the story. Your users don’t benefit until it’s running on their devices. The faster that download completes, the sooner your fix is in their hands. And the leaner your updates are, the better the experience gets for everyone. Faster downloads, lower costs, and a smoother ride as your team and user base grow. In this guide, we’ll walk through how to keep your updates small, ship them with confidence, and get the most out of every push.&lt;/p&gt;

&lt;h2&gt;
  
  
  EAS Update pricing recap
&lt;/h2&gt;

&lt;p&gt;To get the most out of EAS Update, it helps to understand two concepts that shape how your updates are delivered and billed: Monthly Active Users (&lt;em&gt;MAUs&lt;/em&gt;) and bandwidth. Knowing how they work, and the things you can do to keep your updates lean, means faster downloads for your users and lower costs for you. This blog post assumes you’re already familiar with what EAS Update is and what &lt;a href="https://docs.expo.dev/eas-update/introduction/#when-to-use-eas-update" rel="noopener noreferrer"&gt;scenarios it’s useful for&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monthly Active Users (MAUs)
&lt;/h3&gt;

&lt;p&gt;A Monthly Active User (&lt;em&gt;MAU&lt;/em&gt;) is a unique user who downloads at least one update via EAS Update within a single billing period. No matter how many updates they download that month, they still count as one MAU. And if a user checks for updates but nothing new is available (&lt;em&gt;so no download happens&lt;/em&gt;), they’re not counted at all.&lt;/p&gt;

&lt;p&gt;Uninstalling and reinstalling the app will count as a separate MAU the next time they download an update. Similarly, the same person on two different devices would count as two MAUs. These edge cases probably won’t dramatically change your numbers, but they’re good to be aware of if your actual MAU count doesn't exactly match Play and App Store counts or metrics that consider more context about the unique identities of your users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bandwidth
&lt;/h3&gt;

&lt;p&gt;While every update a user downloads consumes bandwidth, the quotas on each &lt;a href="https://expo.dev/pricing" rel="noopener noreferrer"&gt;plan&lt;/a&gt; are generous, and most teams stay comfortably within their plan's allocation. Actual bandwidth usage tends to be much lower than you might expect, thanks to factors like &lt;a href="https://docs.expo.dev/eas-update/estimate-bandwidth/" rel="noopener noreferrer"&gt;compression&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The size of that download depends on what changed. For example, if only your JavaScript was updated, that’s all the user downloads. Assets already on the device from the original build (&lt;em&gt;or from a previous update&lt;/em&gt;) &lt;a href="https://docs.expo.dev/billing/usage-based-pricing/#how-to-optimize-update-usage" rel="noopener noreferrer"&gt;aren’t re-downloaded&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That number is often smaller than you’d think: if you publish three updates in a week and a user only opens your app once that week, they download one update - not three. Only the &lt;a href="https://docs.expo.dev/eas-update/how-it-works/#practical-overview" rel="noopener noreferrer"&gt;latest update is downloaded&lt;/a&gt;, and the rest are skipped. From both a speed and bandwidth perspective, this is great - your users aren’t downloading more than they need to, which means updates are delivered faster and you’re only paying for what’s actually being used.&lt;/p&gt;

&lt;p&gt;That said, keeping your updates lean is still worth doing for a bunch of reasons beyond billing: faster downloads, less data usage for your users, and a snappier experience all around. More about this later.&lt;/p&gt;

&lt;p&gt;One thing we’re really excited about: with SDK 55, you can opt in to &lt;a href="https://docs.expo.dev/eas-update/bundle-diffing/" rel="noopener noreferrer"&gt;Hermes bytecode diffing&lt;/a&gt; (&lt;em&gt;currently in beta, please check it out!&lt;/em&gt;). Instead of downloading a full JavaScript bundle, the client applies a binary diff to what’s already installed. This can reduce download sizes significantly, meaning your users get updates faster and you can ship more frequently. It’s one of the ways we’re actively working to make updates both cheaper to deliver and better for your users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why smaller updates matter
&lt;/h2&gt;

&lt;p&gt;The smaller your updates are, the faster they download for your users - and the less bandwidth each update consumes, both for your EAS Update usage and for your users on cellular or limited data plans. Smaller bundles also mean faster builds, and when your app loads an update on launch, a leaner bundle means a snappier startup. And if your bundle gets large enough, you might even notice it during development - live reload can start to feel slow when it has to process a big bundle on every change. Here are some techniques worth knowing about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimize your assets
&lt;/h3&gt;

&lt;p&gt;Images can add up quickly in your update size, so it’s worth making sure they're optimized before you ship. Compressing your images where possible can make a meaningful difference - often with little to no visible quality loss. The smaller your images, the faster your updates download and the less data your users consume.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let your assets be assets
&lt;/h3&gt;

&lt;p&gt;When an image is a proper asset file, it gets downloaded once (&lt;em&gt;either through a build or an update&lt;/em&gt;) and stays on the device. Future updates skip it entirely if it hasn’t changed. But when assets are inlined in your JavaScript (like for example base64-encoded images), they’re part of the bundle itself, so they come along for the ride on every update.&lt;/p&gt;

&lt;p&gt;For images that change frequently or live outside your bundle entirely, serving them from a CDN paired with &lt;a href="https://docs.expo.dev/versions/latest/sdk/image/" rel="noopener noreferrer"&gt;expo-image&lt;/a&gt; is worth considering. Its default caching policy persists images to disk on first load, so they behave just like bundled assets after that (&lt;em&gt;downloaded once, reused across app restarts&lt;/em&gt;) without adding any weight to your updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choose which assets go into your updates
&lt;/h3&gt;

&lt;p&gt;Not every asset in your project needs to be delivered through updates. With &lt;a href="https://docs.expo.dev/eas-update/asset-selection/" rel="noopener noreferrer"&gt;asset selection&lt;/a&gt;, you can specify exactly which assets should be included using file patterns in your app config. Everything else stays in the native build and never gets downloaded from the update server.&lt;/p&gt;

&lt;p&gt;For example, if only the images in your app/images directory change between releases, you can limit your updates to just those:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"updates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"assetPatternsToBeBundled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"app/images/**/*.png"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you’ve configured your patterns, run &lt;code&gt;npx expo-updates assets:verify&lt;/code&gt; before publishing. This ensures that all required assets will be included when you publish an update - assets that fall through the cracks won't be available, which can cause unexpected behavior or crashes. Check out the &lt;a href="https://docs.expo.dev/eas-update/asset-selection/#verifying-that-an-update-includes-all-required-app-assets" rel="noopener noreferrer"&gt;docs&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Submit to the app stores regularly
&lt;/h3&gt;

&lt;p&gt;When you submit a new build to the app stores, it includes all your latest assets. That means any OTA updates you publish after that only need to include what’s changed since the build, so users download less. If you’ve recently added large or multiple new assets, shipping a new build is a good way to &lt;a href="https://docs.expo.dev/eas-update/optimize-assets/#further-considerations" rel="noopener noreferrer"&gt;keep your next updates lean&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One approach that works well is pairing a regular binary release cadence (&lt;em&gt;say, monthly&lt;/em&gt;) with OTA updates for changes in between. Your users get improvements more frequently, and each new binary resets the asset baseline for your subsequent updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep your JavaScript bundle in check
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmou1urgwtqsgbwktb0h7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmou1urgwtqsgbwktb0h7.jpg" alt="OTA Update optimization" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your JavaScript bundle is part of every update you publish. Tools like &lt;a href="https://docs.expo.dev/distribution/app-size/" rel="noopener noreferrer"&gt;Expo Atlas&lt;/a&gt; give you a visual breakdown of your bundle, showing exactly how much space each dependency takes up. It can be a real eye-opener - you might discover a library you imported for a single utility function is pulling in way more than you expected, or spot a dependency you stopped using but never removed.&lt;/p&gt;

&lt;p&gt;You can try it right now with your local dev server. Start your app with Atlas enabled:&lt;/p&gt;

&lt;p&gt;EXPO_ATLAS=true npx expo start&lt;/p&gt;

&lt;p&gt;Then press &lt;code&gt;shift + m&lt;/code&gt; in the terminal running the dev server to open the dev tools plugin menu and launch Atlas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shipping updates efficiently
&lt;/h2&gt;

&lt;p&gt;So far we’ve covered how to keep your updates lean. But the way you ship updates matters too. Rollouts give you more control over how updates reach your users, and using updates for internal testing can save you time and build credits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use rollouts for production updates
&lt;/h3&gt;

&lt;p&gt;When you publish an update, it’s available to your entire user base by default. Every update your users download counts toward your bandwidth usage, so rollouts are a natural way to be efficient: start with a small group, make sure everything looks good, and then roll it out to everyone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use updates for internal testing and PR previews
&lt;/h3&gt;

&lt;p&gt;During development, getting changes in front of your team for review often means creating a new build. But for changes that don’t involve native code, an OTA update does the same job - and it takes only seconds. Your team can &lt;a href="https://docs.expo.dev/eas-update/preview/" rel="noopener noreferrer"&gt;preview&lt;/a&gt; changes directly on a &lt;a href="https://docs.expo.dev/develop/development-builds/introduction/" rel="noopener noreferrer"&gt;development build&lt;/a&gt; they already have installed, or even from a &lt;a href="https://expo.dev/blog/feature-previews-from-pull-requests" rel="noopener noreferrer"&gt;pull request preview&lt;/a&gt; that’s generated automatically.&lt;/p&gt;

&lt;p&gt;The more of your review and iteration cycle that happens through updates rather than builds, the less time you spend waiting around before anyone can look at a change. Your team can have the latest version on their device in seconds, which means the feedback loop opens up much earlier in the process. That adds up fast when you’re iterating on a feature, and you’re saving build credits along the way. And since it’s just your team downloading these updates, the MAU and bandwidth cost is practically zero, making it one of the best value-for-effort wins on your plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping track of your usage
&lt;/h2&gt;

&lt;p&gt;Everything we’ve covered so far (&lt;em&gt;keeping updates lean, using rollouts, leveraging updates for internal testing&lt;/em&gt;) all contributes to getting more value out of your EAS Update plan. But it’s also nice to know where you can see how things are going.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitor your usage in the dashboard
&lt;/h3&gt;

&lt;p&gt;Expo provides a usage overview on your account’s &lt;a href="https://docs.expo.dev/billing/manage/" rel="noopener noreferrer"&gt;Billing&lt;/a&gt; and &lt;a href="https://expo.dev/accounts/%5Baccount%5D/settings/usage" rel="noopener noreferrer"&gt;Usage&lt;/a&gt; pages. There you’ll find a summary of your EAS Update usage: how many MAUs have downloaded updates and how much bandwidth has been consumed during the current billing cycle (&lt;em&gt;or given time period&lt;/em&gt;). It’s a handy way to stay informed, especially if you’re curious how your numbers look after a big release.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use the pricing calculator to find the right plan
&lt;/h3&gt;

&lt;p&gt;If you’re trying to figure out which subscription plan fits your needs, the &lt;a href="https://expo.dev/pricing#update" rel="noopener noreferrer"&gt;pricing calculator&lt;/a&gt; on the Expo website can help. Adjust the sliders for your expected MAU count, builds, and CI/CD minutes, and it’ll recommend a plan - and if your numbers go beyond what’s included in that plan, it’ll show you the estimated cost of that extra usage too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pick the plan that fits today
&lt;/h3&gt;

&lt;p&gt;You don’t need to have everything figured out before choosing a plan. Start with what fits your current needs - you won’t hit a wall if your app grows faster than expected. Every plan scales naturally with your usage, and you can move between plans as your needs change. Expo’s pricing is designed to grow with you, not get in the way. EAS Update is available on every plan, including the free plan, so you can try it out without any commitment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;At the end of the day, EAS Update gives you a pretty sweet deal: ship updates to your users in seconds, iterate faster with your team, and scale at your own pace. Leaner updates, rollouts, and a few of the techniques we covered are all it takes to deliver a faster experience for your users while getting more value out of your plan.&lt;/p&gt;

&lt;p&gt;Ready to get started? &lt;a href="https://docs.expo.dev/eas-update/getting-started/" rel="noopener noreferrer"&gt;Set up EAS Update&lt;/a&gt; in your project - it only takes a few minutes to set up, and you can start shipping updates right away. If you want to dive deeper into any of the topics we covered, the &lt;a href="https://docs.expo.dev/eas-update/introduction/" rel="noopener noreferrer"&gt;EAS Update docs&lt;/a&gt; are a great next step. We’d love to hear about your success story with EAS Update too!&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>otaupdates</category>
    </item>
  </channel>
</rss>
