Quickstart

Create an account, connect your React Native app with one command, and ship your first over-the-air update — Expo and bare React Native, about ten minutes end to end.

Step 1 · 1 min

Create your account

Nothing to install or host. Sign up, and you're ready to connect your first app — no card, no payment step.

Every sign-in sends a 6-digit code

Two-factor is mandatory and has no opt-out — anyone who can sign in to your account can push JavaScript into your users' installed apps. CI uses an API key instead, which skips the code.

Step 2 · 2 min

Install and connect

Run this in your React Native project root:

terminal
npm install @otaupdate/react-native     # the SDK, in your app
npm install -g @otaupdate/cli           # the CLI, for publishing

ota login                               # email + 6-digit code
ota init                                # once per app

Prefer not to install globally? Every ota command also works as npx @otaupdate/cli ….

ota init reads your package.json, app.json and native config to work out the app name, package manager, version and bundle identifiers, then prints this:

terminal
✔ Created project "MyApp" (my-app)
✔ Wrote ota.config.js
✔ Scripts added        ota:staging, ota:prod, ota:promote, ota:rollback, ota:doctor

  SDK keys — one per channel, each used by both platforms.
  production   ota_live_xxxxxxxxxxxxxxxxxxxxxxxx
  staging      ota_test_xxxxxxxxxxxxxxxxxxxxxxxx

Copy the staging key — you'll paste it in the next step, and ship your first update to staging rather than to real users. Both keys stay readable from the project page in the dashboard, so there is nothing to lose here.

Re-running ota init is safe: it repairs the config and scripts rather than creating a second project.

The key is what picks the channel

An SDK key belongs to one channel, and the same key covers Android and iOS — the device reports its own platform. Because the channel travels inside the binary, a staging build cannot pull production updates even if something else is misconfigured.

Step 3 · 5 min

Put the key in your build

This is the only step that differs by project type. Pick your tab — if your project has an app.json with an expo block, you're on Expo.

Add the plugin to app.json with the key from step 2:

app.json
{
  "expo": {
    "plugins": [
      ["@otaupdate/react-native", { "deploymentKey": "ota_test_xxxxxxxxxxxxxxxxxxxxxxxx" }]
    ]
  }
}

Then generate the native projects:

terminal
npx expo prebuild --clean

That's everything — the plugin writes the iOS and Android configuration and points React Native at the OTA bundle. You do not edit a native file by hand. Confirm it landed:

terminal
grep ota_ android/app/src/main/res/values/strings.xml
# <string name="ota_deployment_key" translatable="false">ota_test_…</string>
Expo details →

Step 4 · 1 min

Add one line to your app

Wrap your root component. For most apps this is the entire integration:

App.tsx
import { withOtaUpdate } from '@otaupdate/react-native';

function App() {
  return <YourApp />;
}

export default withOtaUpdate(App);

That single line checks for updates on launch and on every foreground, downloads in the background, installs on the next restart, and confirms the running bundle so a broken update rolls itself back.

Don't skip the wrapper

Confirming the running bundle is what arms rollback protection. Without withOtaUpdate — or your own notifyAppReady() call — every update you ship reverts on the next launch, which looks exactly like "the update never arrived". See Wire up your app for the manual version.

Step 5 · 3 min

Rebuild once, then ship

Rebuild the app one time — that's what puts the SDK in the binary. It must be a release build: a debug build loads JS from Metro and never looks at the OTA bundle.

terminal
npx expo run:android --variant release        # Expo
npx react-native run-android --mode release   # bare RN

Now change something visible in your app — a heading, a colour — and ship it. No rebuild this time:

terminal
npm run ota:staging
terminal
✔ Released #1 to staging
  android  4.2 MB
  ios      4.4 MB
  commit 9f3c1ab (main)

Fully close the app on the device and open it again. The change is there. That's an update shipped without a store review.

Didn't work?

Run npm run ota:doctor first — it checks the project, bundle identifiers, git state and scripts, and names the fix. The four things that account for nearly every failed first attempt:

SymptomCauseFix
Nothing changes, no errorsDebug buildRebuild in release mode — Metro serves the bundle in debug.
The change appears, then disappearsnotifyAppReady() never reachedWrap the root component in withOtaUpdate (step 4).
native module … is not availableNative side not rebuilt, or Expo Gopod install + an Xcode build, a Gradle build, or npx expo prebuild --clean. Expo Go has no native module.
Update applies only on restartWorking as designedThe default is ON_NEXT_RESTART. A foreground refresh will not apply it.

When you're ready for real users

terminal
npm run ota:promote          # serve the staging bytes from production
npm run ota:prod             # or publish straight to production
npm run ota:rollback         # undo it, reaching devices that already updated

Production builds carry the production key instead of the staging one — same steps, different value in step 3.