Semantic versioning for OTA bundles and native runtimes
How to version OTA bundles and keep them compatible with the native shells in the wild. A practical semver strategy for Capacitor live updates using runtime versions and channels.
Versioning gets confusing with OTA because there are really two versions in play: the native app version that goes through the store, and the web bundle version that ships over the air. Keep them straight and releases stay predictable. Blur them and you get bundles reaching shells that can't run them. This guide lays out a simple, durable scheme for OtaKit.
Mental model: the store version answers “which native shell is this?” The bundle version answers “which web build is this?” A third value — runtime version — connects them.
The three numbers that matter
| Value | What it identifies | Changes when |
|---|---|---|
| App version (store) | The native binary users installed | You submit to the App Store / Play |
| Bundle version | A specific web build | Every OTA release |
| Runtime version | Which shells a bundle is compatible with | Native code changes |
Version bundles with semver
Treat each OTA bundle like a package release. A pragmatic reading of semver for web bundles:
- Patch (1.4.1) — bug fixes, copy, styling. Safe, frequent, background updates.
- Minor (1.5.0) — new features that don't need new native capabilities. Still OTA-able.
- Major (2.0.0) — usually paired with a native change, which means a store release and a runtime version bump.
Runtime version is the compatibility contract
This is the one that prevents crashes. When a store build changes native code — a new plugin, a permission, a Capacitor upgrade — bump runtimeVersion. Bundles are matched to compatible shells, so a bundle built for the new native surface never lands on an old install that can't run it.
// capacitor.config.ts
const config: CapacitorConfig = {
appId: "com.example.myapp",
appName: "My App",
webDir: "dist",
plugins: {
OtaKit: {
appId: "YOUR_OTAKIT_APP_ID",
runtimeVersion: "2", // bump when native code changes
},
},
};OtaKit also checks your bundle's dependencies at upload time and warns when it detects native code the target shells don't have — a safety net on top of the version you set.
A workable convention
- Keep the native app version and runtime version in lockstep for store releases.
- Increment bundle patch/minor freely for OTA releases against the same runtime.
- When you cut a store release with native changes, bump runtime version and start the new bundle line there.
If a change forces a runtimeVersion bump, it's a store release — not an OTA update. That single rule keeps your versioning honest.
Where to go next
See channels & runtime version for the API, and common OTA mistakes for the versioning traps to avoid.