Ship every commit
Every merge to main becomes an over-the-air (OTA) update on your users' devices — one workflow file, a staged rollout, auto-pause watching.
.github/workflows/release.yml
The whole pipeline: the merge builds, uploads, signs and releases to production; devices take it on their next check.
Release to production
Two workflows: merge to main and it releases to production; open a pull request and it gets a preview channel that cleans up after itself.
Forward-compatible data, because devices move backwards
A rollback, a revocation or a failed start runs older code over data the newer code wrote.
A schema change adds; it never renames, removes or retypes
Migrations run forward only; a store release retires old shapes
Code keeps the fields it does not know
Diagram: three releases with an arrow from the newest back to the oldest, and the device's local data beneath — an added field the older release keeps unread, a renamed field it cannot read
Native changes wait for the store
An OTA update replaces the JavaScript or web layer, never the native one. A device takes a release only when its store build matches.
No channel per native version, no version to type
Compatibility is worked out at upload, not typed
The dry run shows the honest zero before the merge
Diagram: the audience count three times — the pull request's dry run after a native change reaches 0 devices with a warning that the build is not known yet; the merge's release stays at zero; the first merge after the store build ships reaches the devices on it
Rollout progression: auto-pause, run in reverse
One line in the workflow stages every merge: the release starts small and widens on its own while failures stay low.
The release starts small and widens in steps
Auto-pause stops the rollout when failures climb
It waits visibly until enough devices have tried
Diagram: a step chart widening in stages to a hundred percent; a dashed line from a middle step down marks an auto-pause
Runs in your pipeline
One CLI line after your build. Recipes for every provider below, and a preview channel per pull request.
Which commit is live? The release log says
Every bundle records the commit it was built from — read by the CLI at upload, no repository connection, nothing to type.
At upload the CLI reads the commit behind the build. The release log shows it, linked to the commit: the merge is the release, and the release names the merge. A bundle uploaded from a tree with uncommitted changes is marked as such, since it may not match its commit. Pipelines that build in one job and deploy in another hand the commit over.
Diagram: the production release log with a commit column — release #43 from ab12c3f "fix: cart crash", #42 from 9e4d1f0 "feat: address form", #41 from 7d2e9a4 "chore: bump deps", marked dirty
The same file for every framework
React Native and Expo bring source and end with one release per platform. Capacitor and Cordova build first and release once for both.
React Native
Brings source. The CLI bundles your JavaScript per platform inside the upload, so the workflow needs no build line of its own and ends with one release per platform. The same file also opens a preview channel for every pull request.
React Native code pushExpo
Brings source too. The CLI bundles inside the upload, and compatibility with each store build is worked out for you, with no version to bump. The workflow ends with one release per platform, and a deep link opens each preview on a real phone.
Expo OTA updatesCapacitor
Brings its built web directory, the one your project already configures. The pipeline runs your own web build first, then one release line serves both platforms, and a deep link switches a test device to a pull request’s preview channel.
Capacitor live updatesCordova
Brings www, its built web directory. Build first, release once for both platforms, on tooling that stays maintained. The workflow is the same file as Capacitor’s, the web build step included, and a deep link opens each pull request’s preview channel on a test device.
Cordova code push
The pipeline is one file.
Your next merge ships itself.
Questions? Join our Discord or contact us.