7 common Capacitor OTA update mistakes (and how to avoid them)
The failure modes that bite teams shipping live updates: skipping notifyAppReady, breaking native compatibility, no rollback, shipping to everyone at once, ignoring bundle size. What goes wrong and the fix.
Over-the-air updates are simple to start and easy to get subtly wrong. The failures below are the ones that actually bite teams in production — none of them are exotic, and all of them are avoidable once you know to look. Here they are, worst first, with the fix.
1. Forgetting notifyAppReady()
This is the big one. OtaKit treats every freshly activated bundle as unproven and waits for your app to confirm it booted. If you never call notifyAppReady(), the device assumes the update failed and rolls back — so your update “doesn't stick,” even though nothing was actually broken. Call it once, after your app has mounted and rendered:
import { OtaKit } from "@otakit/capacitor-updater";
await OtaKit.notifyAppReady();The flip side is a feature, not a bug: this same handshake is what makes a genuinely broken release roll back automatically instead of stranding users on a white screen.
2. Shipping to everyone at once
Auto-rollback catches bundles that crash on boot. It does not catch a broken checkout, a bad API call, or a layout that only fails on one device class — those boot fine and still cause damage. Releasing to 100% of users on every update means every such bug is a full- fleet incident. Use a beta channel first; see staged rollouts.
3. Breaking native compatibility
OTA moves the web layer only. If a bundle calls a Capacitor plugin the installed native shell doesn't have — because you added it in a store build that hasn't rolled out yet — it breaks on older installs. The fix is runtimeVersion: bump it whenever a store release changes native code, so each shell only receives bundles it can run. OtaKit warns at upload time when it detects a mismatch.
Rule of thumb: if a change touches ios/, android/, plugins, permissions, or the Capacitor version, it's a store release with a runtimeVersion bump — not an OTA update.
4. Rebuilding instead of promoting
A common process smell: validating a bundle on a beta channel, then running a fresh build to release to production. The production artifact is now different from the one you tested. Promote the exact bundle id you validated instead:
otakit release <bundle-id> --channel production
5. Trying to OTA things you can't
Native code, new permissions, entitlements, splash screens baked into the binary, and the Capacitor runtime itself cannot ship over the air — and shouldn't, because that's the exact line Apple and Google draw. Trying to force it leads to rejections or broken installs. Know what's OTA-able: everything in your web build; nothing native. See App Store-compliant OTA updates.
6. Ignoring bundle size on asset-heavy apps
If your app carries large images or media, a full-zip update re-downloads all of it every release, even when only a line of JavaScript changed. On flaky mobile connections that means failed or slow updates. Switch on delta updates so devices download only the files that changed:
otakit upload --release --strategy deltas
7. No token discipline in CI
Release automation is great until a token leaks. Don't hardcode credentials in workflow files or echo them to logs; inject a scoped token as a masked secret and rotate it when people leave. The GitHub Actions guide shows a safe setup.
The through-line: OTA is safe when a bad or malicious release degrades into a non-event. notifyAppReady, staged channels, runtimeVersion, and hash-verified bundles are the four controls that get you there.
Where to go next
Read how OTA works for the full model, and OTA update security to lock the pipeline down.