Integrate the SDKUpdated

Expo

Use FireOTA in an Expo app: one config plugin, works with expo prebuild, EAS Build and expo-router.

7 sections Edit this page on GitHub

Requirements

  • Expo SDK 50 or newer, with development or release builds (npx expo run:android, EAS Build). Expo Go cannot load native modules, so it can't run FireOTA.
  • Works with Continuous Native Generation: you don't need to commit android/ and ios/. The config plugin re-applies FireOTA's native change on every expo prebuild and EAS Build.
  • No expo-updates. Both replace the JavaScript bundle at launch; the plugin stops the build if it is installed. See migrating from EAS Update, or keep expo-updates and use Expo Updates without EAS instead of the SDK.
  • Tested with Expo SDK 58 (React Native 0.88, expo-router): prebuild, OTA bundling and the Android release compile are verified. On-device OTA runs and iOS (same plugin) are still being tested.

Install

Run this in your Expo project. fireota init detects Expo and wires everything for you:

bash
npx expo install @fireota/react-native
npm install --save-dev @fireota/cli
npx fireota init --app YOUR_APP_ID --server-url https://fireota.example.com
  • fireota.config.js — app ID, server URL and channel (no secrets).
  • app.json — adds the "@fireota/react-native" config plugin.
  • Your root component — with expo-router the root layout (app/_layout.tsx), otherwise registerRootComponent(App) — is wrapped with withFireOTA, which checks for updates and shows the update prompt.
  • package.json — an ota:publish script; .gitignore — update packages and the signing key.
Note: Using app.config.js or app.config.ts? init doesn't edit dynamic configs — add "@fireota/react-native" to plugins yourself.

Set it up by hand

Prefer to see every change? These are the only two edits FireOTA needs in an Expo app.

  1. 1
    Add the plugin to app.json. The runtime version is your expo.version (Expo writes it into the native project), so bump it whenever native code changes:
    json
    {
      "expo": {
        "version": "1.0.0",
        "plugins": [
          "expo-router",
          "@fireota/react-native"
        ]
      }
    }
  2. 2
    Wrap your root component. With expo-router:
    tsx
    // src/app/_layout.tsx (or app/_layout.tsx)
    import { Stack } from "expo-router";
    import { withFireOTA } from "@fireota/react-native";
    import fireotaConfig from "../../fireota.config";
    
    function RootLayout() {
      return <Stack />;
    }
    
    export default withFireOTA(RootLayout, fireotaConfig);
    Without expo-router:
    tsx
    // index.ts (apps without expo-router)
    import { registerRootComponent } from "expo";
    import { withFireOTA } from "@fireota/react-native";
    import fireotaConfig from "./fireota.config";
    import App from "./App";
    
    registerRootComponent(withFireOTA(App, fireotaConfig));

On prebuild, the plugin makes release builds load the latest verified OTA bundle: on Android it passes jsBundleFilePath to ExpoReactHostFactory (older SDKs: overrides getJSBundleFile()); on iOS bundleURL() returns FireOTA's bundle. Debug builds keep using Metro.

Build and publish

Make one release build so the app contains the SDK and the plugin's change:

bash
# Local release build (Android): prebuild runs the FireOTA plugin
npx expo run:android --variant release

# Or EAS Build: the plugin runs on EAS's servers too
eas build --platform all --profile production

After that, ship JavaScript changes as updates. The CLI bundles Expo apps with expo export:embed and the same entry point your native build uses (expo-router included), then compiles Hermes bytecode unless "jsEngine": "jsc":

bash
# Bundles with Expo (expo export:embed + Hermes), exactly like the store build
FIREOTA_URL=https://fireota.example.com FIREOTA_API_KEY=fota_sk_... \
  npm run ota:publish -- --message "Fix checkout crash"

Keep expo-updates, skip EAS UpdateNew

Already using expo-updates? You can keep it and point it at FireOTA instead of EAS Update. You don't need the FireOTA SDK or any native change. Bundles are stored in your own Firebase Storage, and you still get channels, rollouts and rollbacks.

Tip: The full step-by-step guide, with screenshots from a real device, is in Expo Updates without EAS.

Migrating from EAS Update (expo-updates)

  1. 1
    Remove expo-updates and its config:
    bash
    npm uninstall expo-updates
    # remove "updates" and "runtimeVersion" from app.json, then rebuild
    npx expo prebuild --clean
  2. 2
    Run npx fireota init … as above and ship a new store build. Users on old builds keep receiving EAS updates until they install it.
  3. 3
    Map EAS channels to FireOTA channels (production, beta, …). Keep FireOTA runtime versions equal to expo.version.
Caution: Need both during a migration? Pass ["@fireota/react-native", { "allowExpoUpdates": true }] and set "updates": { "enabled": false }, or the two systems will fight over which bundle launches.

Troubleshooting

  • Build fails with “expo-updates is installed” — remove it (see above); FireOTA cannot run next to it.
  • Prebuild warns “could not find the expected code” — your MainApplication or AppDelegate was customised. Apply the change from Android setup / iOS setup in your own config plugin.
  • Update downloads but never applies — you're running a debug build or Expo Go; test with npx expo run:android --variant release.
  • “No compatible update” — expo.version of the installed build must equal the update's runtime version.

Was this page helpful?