Staged rollouts for Capacitor live updates
Ship OTA updates to a slice of users first, watch the health signals, then promote to everyone — or roll back. How to run staged rollouts safely with channels, runtime versions, and OtaKit.
Shipping a live update to every device at once is fast — and occasionally a fast way to take your whole user base down. A staged (or phased) rollout releases to a small slice first, lets you watch it in the wild, and only then promotes to everyone. If something looks wrong, you stop before it spreads.
This guide shows how to run staged rollouts for Capacitor apps using channels and runtime versions, with OtaKit as the example.
Mental model: a channel is an audience decision (who sees this bundle). A runtime version is a compatibility contract (which native shells can run it). Staged rollouts are mostly about the first.
Why stage a rollout at all
OTA already gives you automatic rollback: a bundle that fails to boot rolls back on its own. But not every bad release crashes on launch. A broken checkout flow, a layout that only breaks on one device class, a subtle data bug — these boot fine and still hurt. Staging limits the blast radius of the failures rollback can't catch, and gives you real usage signal before you commit.
The channel-based pattern
The simplest reliable approach uses two channels: a small pre-production audience and production. You release the same bundle to the small channel first, watch, then promote the exact same bundle — not a rebuild — to production.
- Point internal users, beta testers, or a fraction of installs at a
betachannel via the plugin config. - Release to
betaand let it soak for a defined window (an hour, a day — your call). - Watch crash-free sessions, key funnels, and error rates for that cohort.
- Promote the same bundle to production, or hold and fix.
# release to the small audience first otakit upload --release beta # looks healthy? promote the exact same bundle — no rebuild otakit release <bundle-id> --channel production
Promoting the same bundle id matters: you're shipping the artifact you already validated, byte for byte, not a fresh build that might differ.
Assigning devices to a channel
Set the channel in the plugin config for a static split (all beta builds on the beta channel), or move a device at runtime for opt-in beta programs:
import { OtaKit } from "@otakit/capacitor-updater";
// e.g. behind a "Join the beta" toggle in settings
await OtaKit.setChannel({ channel: "beta" });For a percentage-based rollout, gate that assignment behind your own flag — assign a stable fraction of installs to beta and widen it as confidence grows. See channels & runtime version for the full surface.
What to watch during the soak
- Crash-free sessions for the cohort vs. baseline — the fastest red flag.
- notifyAppReady rate — if the new bundle boots cleanly, this stays high; a dip means devices are rolling back.
- Business funnels — the bugs rollback can't catch show up here.
When it goes wrong: stop and roll back
If the beta cohort looks bad, don't promote — and if a bad bundle already reached production, release the previous known-good bundle to the production channel. Because promotion is just pointing a channel at a bundle id, rolling forward to the old version is as fast as rolling out the new one was.
# emergency: point production back at the last good bundle otakit release <previous-bundle-id> --channel production
For genuine emergencies where you need devices to update immediately rather than on next launch, releasing with --force-immediate makes them apply and reload on their next check. Use it sparingly — see update strategies.
A staged rollout turns “we shipped a bug to everyone” into “we caught a bug on the beta channel.” Same speed, far less risk.
Where to go next
Combine this with a secure pipeline and automated releases, and read common OTA mistakes to avoid the traps that make rollouts risky in the first place.