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
Step 2 · 2 min
Install and connect
Run this in your React Native project root:
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 appPrefer 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:
✔ 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_xxxxxxxxxxxxxxxxxxxxxxxxCopy 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
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:
{
"expo": {
"plugins": [
["@otaupdate/react-native", { "deploymentKey": "ota_test_xxxxxxxxxxxxxxxxxxxxxxxx" }]
]
}
}Then generate the native projects:
npx expo prebuild --cleanThat'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:
grep ota_ android/app/src/main/res/values/strings.xml
# <string name="ota_deployment_key" translatable="false">ota_test_…</string>Step 4 · 1 min
Add one line to your app
Wrap your root component. For most apps this is the entire integration:
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
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.
npx expo run:android --variant release # Expo
npx react-native run-android --mode release # bare RNNow change something visible in your app — a heading, a colour — and ship it. No rebuild this time:
npm run ota:staging✔ 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:
| Symptom | Cause | Fix |
|---|---|---|
| Nothing changes, no errors | Debug build | Rebuild in release mode — Metro serves the bundle in debug. |
| The change appears, then disappears | notifyAppReady() never reached | Wrap the root component in withOtaUpdate (step 4). |
native module … is not available | Native side not rebuilt, or Expo Go | pod install + an Xcode build, a Gradle build, or npx expo prebuild --clean. Expo Go has no native module. |
| Update applies only on restart | Working as designed | The default is ON_NEXT_RESTART. A foreground refresh will not apply it. |
When you're ready for real users
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 updatedProduction builds carry the production key instead of the staging one — same steps, different value in step 3.