Self-hosted · MIT · React Native & Expo
Ship JavaScript today.
Know what happened by tonight.
Open OTA publishes updates to installed apps without a store review — and then answers the questions other tools leave open: who got it, who is running it, who rolled back. Every release signed. Every rollout a number. Yours to run.
npx @open-ota/cli init$ ota publish --channel production --rollout 10 ✓ expo export (android) · 4.8 MB · b94d27b9… ✓ uploaded straight to storage, signed by the server LABEL PLATFORM CHANNEL ROLLOUT SHA256 v42 android production 10% b94d27b9934d $ ota metrics --channel production RELEASE DEVICES SHARE READY ROLLBACK v42 8,250 82.5% 99.6% 0.4% v41 1,200 12.0% 99.8% 0.2% v40 550 5.5% 99.7% 0.3%
Signed, end to end
A key pair per project. The device verifies the manifest signature and the bundle digest before a byte runs — a compromised CDN can only serve bytes that fail the hash.
Rollback on one strike
A watchdog flag hits the disk before React Native gets the bundle. Crash before the app confirms a healthy start, and the next launch reverts — and never re-offers that release to that device.
Rollout you can reason about
sha256(device:release) % 10000. Deterministic, stateless, salted per release. Raising a percentage only ever adds devices.
Adoption at flat cost
The update check is the heartbeat. One row per device, daily counters per release — O(devices), never O(events). A million installs is the same shape, ten times over.
Never lands on the wrong binary
Releases pin to a native fingerprint and a per-build floor. Change a native dependency and old bundles stop being offered — structurally, not by convention.
A QR away from any release
A signed, expiring deep link installs one exact release on one phone, pinned, without touching the global rollout. Knowing the release id is not enough.
The half other update tools skip
Distribution by OTA release and by native version, funnel and rollback rate per release, adoption over time. Live from your own Postgres.

One codebase, wherever you run it
┌──────────────────────────────┐
devices │ app │ admin
─────────────│ @open-ota/react-native │──────────────────────────
└──────┬───────────────┬───────┘
│ check+events │ download ota CLI · ota mcp · dashboard
▼ ▼ │
┌────────────┐ ┌───────────┐ ▼
│ Device API │ │ CDN │ ┌────────────┐
│ │ └─────┬─────┘ │ Admin API │
└─────┬──────┘ │ └─────┬──────┘
│ ▼ │ signed upload
▼ ┌───────────────┐ ▼
┌──────────┐ │ R2 · S3 · MinIO│◀─────────────┘
│ Postgres │ └───────────────┘
└──────────┘ota init --provider supabasedocker compose upBundles never pass through the API: the CLI hashes locally and writes straight to the bucket — which is what lets the same server run inside an edge function.
Operated by people. Or by agents.
The server is an MCP endpoint with OAuth built in. Point a client at it, sign in through the browser, and fifteen tools cover publish, promote, rollout, rollback, metrics and QR generation — the same contract over remote HTTP and local stdio, held together by a conformance test.
claude mcp add --transport http ota https://your-server/mcp- “Publish the current build to staging.”
- “What percentage is still on v41?”
- “Is v52 rolling back more than the one before it?”
- “Roll v53 out to 10%.”
Where it stands
Pre-release. Server, SDK, CLI, dashboard and MCP are implemented, with 198 tests — including an end-to-end suite on a real embedded Postgres. Nothing has run on a physical phone yet, and the limitations page keeps that list honest. If you want to kick the tires first, there is aone-command demo server with a week of seeded traffic.