Over-the-air (OTA) updates ship JavaScript and asset changes to installed native apps without going through TestFlight or the Play Store. One works with two OTA libraries — pick the one that matches your hosting story. The framework handles the integration automatically; your repo follows the standard install flow.
| expo-updates (EAS Update) | hot-updater | |
|---|---|---|
| Backend | Managed by Expo (EAS subscription) | Self-hosted (Supabase, Cloudflare R2, S3, custom) |
| Setup | npm install expo-updates + eas init | npx hot-updater init + pick storage/database |
| Vendor lock-in | EAS | None |
Standard install:
yarn
npm
bun
pnpm
Add the EAS project URL and runtime policy to app.json:
appVersion is the supported policy. fingerprint isn’t fully wired up yet.
Set the main field in package.json so expo export (which eas update invokes) finds One’s router entry:
Publish via the standard EAS flow:
Terminal
That’s the entire user surface — no babel.config / metro.config in your repo. One starters include "postinstall": "one patch || true" so EAS workers generate the Metro/Babel shims with your real one() router/setup options during install.
Standard install + init:
yarn
npm
bun
pnpm
Wrap your app:
Publish:
yarn
npm
bun
pnpm
See hot-updater.dev/docs for full backend setup.
Only publish OTA updates for changes that don’t touch the native binary:
app.json fields that affect native output, any native iOS/Android fileFor those, bump your app version, build a fresh TestFlight/Play binary, and only then publish OTA updates against the new runtime. The appVersion strategy (supported by both libraries) gates delivery so devices on the old version don’t receive incompatible updates.
eas update from your own machine works the same way EAS workers run it — but One’s auto-setup only fires when CI=1 or EAS_BUILD=true is set, so your local working tree stays clean. After dependencies are installed, generate the same shims explicitly before publishing:
Terminal
The first command sets up the bundler-config files Metro needs. They’re regenerated whenever one patch runs in CI/EAS — gitignore them or leave them out of source control entirely. Most projects publish from EAS Workflows or CI instead and never need this.
If you’d rather commit babel.config.cjs / metro.config.cjs and customize them (extra babel plugins, custom Metro resolvers, etc.), generate them once:
yarn
npm
bun
pnpm
This writes shim files (delegating to one/babel-preset and one/metro-config) without the @one/generated marker comment. After ejecting:
A typical ejected metro.config.cjs looks like:
Pass --force to overwrite existing files.
Edit this page on GitHub.