Migration14 min read

How to migrate from Capgo or Capawesome to OtaKit

A production-grade migration guide: exact config and API translations from Capgo and Capawesome to OtaKit, what maps cleanly, and a safe cutover plan for your install base.

This guide assumes your Capacitor app already runs Capgo or Capawesome Live Update in production, and walks through the move to OtaKit: what maps one-to-one, what maps to a different (usually smaller) concept, and how to cut over without breaking the install base you already have in the field.

The one constraint you can't skip

An OTA migration is a plugin migration. Devices running your current store build have the Capgo or Capawesome plugin compiled into the binary, and they will keep checking that vendor's endpoints until their user installs a store build that contains OtaKit instead. There is no server-side switch that reroutes an existing binary.

Plan for a dual-system window: old binaries keep receiving updates from the old vendor, new binaries receive them from OtaKit. It typically lasts as long as your store-update adoption curve — weeks, not days.

Step 0: inventory your routing model

Before touching code, write down how your current setup decides which device gets which bundle: every channel, every runtime override, every min/max/semver native-version rule, every percentage rollout. Migrations go wrong when this list lives in three heads and a dashboard.

Nearly everything on that list collapses into four OtaKit translations:

  • Audience → channel (with setChannel() for user-facing opt-ins).
  • Native compatibility → runtimeVersion.
  • Release pacing → channel promotion.
  • Per-user behavior → feature flags in your app, outside the OTA transport.

Migrating from Capgo

Config translation

A typical Capgo production config:

// capacitor.config.ts
plugins: {
  CapacitorUpdater: {
    appId: "com.example.app",
    autoUpdate: true,
    defaultChannel: "production",
    directUpdate: false,
    periodCheckDelay: 600,     // seconds
    appReadyTimeout: 10000,
    publicKey: "YOUR_PUBLIC_KEY"
  }
}

The OtaKit equivalent:

// capacitor.config.ts
plugins: {
  OtaKit: {
    appId: "app_xxxxxxxx",
    channel: "production",
    // defaults shown explicitly:
    launchPolicy: "apply-staged",   // activate staged bundles on cold start
    resumePolicy: "shadow",         // download in background on resume
    checkInterval: 600000,          // milliseconds, not seconds
    appReadyTimeout: 10000
  }
}
  • defaultChannelchannel, directly.
  • periodCheckDelay is seconds; OtaKit's checkInterval is milliseconds. This is the classic migration typo — 600 becomes 600000.
  • directUpdate translates by activation timing: false (apply on next launch) is OtaKit's default launchPolicy: "apply-staged"; "always" maps to launchPolicy: "immediate"; "atInstall" maps to runtimePolicy: "immediate" — which is also OtaKit's default, so fresh installs catch up fast out of the box.
  • Capgo's publicKey encryption maps to OtaKit's end-to-end encryption: run otakit generate-encryption-key, ship the key in bundleKeys, upload with --encrypt. See Security for the rotation story.

Runtime API translation

CapgoOtaKitMigration note
notifyAppReady()notifyAppReady()Identical name, identical job. Wire it into your real app-ready path.
getLatest()check()Ask whether a newer bundle exists for the current lane, without downloading.
download()download()Same manual staging step; OtaKit verifies the hash before staging.
next() / set() + reload()apply() or update()apply() activates the staged bundle; update() is the one-call "get me current" helper.
setChannel() / getChannel()setChannel() / getChannel()Direct mapping. OtaKit persists the override across launches; pass null to clear it.
unsetChannel()setChannel({ channel: null })Clearing the override returns the device to the configured channel.
listChannels()Dashboard / CLIChannel discovery is an operator concern in OtaKit, not a device API.
setCustomId()App-level feature flagsKeep per-user targeting in your product logic; OTA decides versions, not cohorts.
reset()Automatic rollback + release rollbackRecovery is health-driven on device and one click per channel in the dashboard.

Method names may drift between plugin major versions — treat the table as the conceptual map and your IDE as the source of truth.

Version targeting

Where Capgo routes by native version metadata on the bundle, OtaKit inverts it: the app declares its lane. Each store build sets a runtimeVersion in config, and releases into that lane can only reach shells that declared it.

# Capgo
npx @capgo/cli bundle upload --channel production --native-version "2.0.0"

# OtaKit: the store build declares runtimeVersion: "2.0.0" in config,
# and you release into that lane
otakit upload --release production

If your Capgo setup uses semver ranges or version filters, name each native baseline explicitly and release per lane. It's more verbose to say and much easier to operate — the compatibility contract is visible in one config line instead of spread across upload flags.

Migrating from Capawesome

Config translation

A typical Capawesome Live Update production config:

// capacitor.config.ts
plugins: {
  LiveUpdate: {
    appId: "6e351b4f-69a7-415e-a057-4567df7ffe94",
    defaultChannel: "production",
    autoUpdateStrategy: "background",
    readyTimeout: 10000,
    publicKey: "YOUR_PUBLIC_KEY",
    autoDeleteBundles: true,
    autoBlockRolledBackBundles: true
  }
}

The OtaKit equivalent:

// capacitor.config.ts
plugins: {
  OtaKit: {
    appId: "app_xxxxxxxx",
    channel: "production",
    launchPolicy: "apply-staged",
    resumePolicy: "shadow",
    appReadyTimeout: 10000
  }
}
  • defaultChannelchannel; readyTimeout appReadyTimeout.
  • autoUpdateStrategy: "background" is OtaKit's default behavior: shadow on resume, apply-staged on launch. Bundles download silently and activate on the next cold start.
  • autoDeleteBundles and autoBlockRolledBackBundles have no OtaKit equivalents because the behaviors are built in: the plugin keeps exactly current + fallback + staged, and a rolled-back bundle is never re-activated.
  • Capawesome's public-key bundle signing maps to OtaKit's always-on signed manifests (ES256) plus SHA-256 verification; if you also need content secrecy, add --encrypt.
  • If you used Capawesome's delta updates, upload with --strategy deltas — same benefit, per-file content-addressed delivery.

Runtime API translation

CapawesomeOtaKitMigration note
ready()notifyAppReady()Same readiness handshake, different name.
sync()update() — or check() + download() + apply()update() covers the common case in one call; the split API gives full control.
fetchLatestBundle()check()Same job: query the lane for a newer version.
downloadBundle()download()Same manual staging step.
setNextBundle()download() + apply()OtaKit always moves to the newest compatible bundle instead of pinning by ID.
setChannel() / getChannel()setChannel() / getChannel()Direct mapping, persisted across launches.
addListener(...)addListener(...)Map to updateAvailable, updateStaged, updateApplied, downloadFailed, rollback.
reset()Automatic rollback + release rollbackHealth-confirmed activation replaces the manual reset button.

Version ranges → runtime lanes

Capawesome's per-platform min/max/eq version codes become explicit lanes: each native baseline declares one runtimeVersion, and you release into the lanes you still support. Ranges disappear; what remains is a list you can read aloud.

# Capawesome
npx @capawesome/cli apps:bundles:create \
  --channel production --android-max 12 --ios-max 12

# OtaKit: one lane per supported native baseline
otakit upload --release production   # runtimeVersion "12" build
otakit upload --release production   # run again from the "11" branch if you still ship fixes there

Feature-by-feature map

You use todayOtaKit equivalentNotes
Channels / audienceschannel + setChannel()Named channels with promotion; runtime overrides for opt-in flows like beta toggles.
Native version targetingruntimeVersionOne explicit compatibility lane per native baseline instead of min/max/semver rules.
Delta updates--strategy deltasPer-file, content-addressed objects; devices download only changed files.
End-to-end encryption--encrypt + bundleKeysAES-256-GCM with your key; generate it with otakit generate-encryption-key.
Forced/critical updates--force-immediateDevices apply and reload on their next check — for emergency fixes.
Staged rollout percentagesChannels + promotionPromote qa → beta → production explicitly instead of percentage math in the updater.
Update lifecycle eventsaddListener()updateAvailable, updateStaged, updateApplied, downloadFailed, rollback.
Per-device targetingFeature flags in your appDeliberately not an OTA concern in OtaKit — ship compatible code, gate features in product logic.

The one deliberate gap: OtaKit has no per-device targeting inside the OTA transport. Every production use we've seen for it — support builds, VIP cohorts, gradual exposure — is better served by feature flags in the app, where product logic already lives. OTA decides which version a lane runs; your app decides which features a user sees.

The cutover plan

  1. Freeze routing changes on the old system except for active incidents.
  2. Document every channel, override, rollout rule, and version rule (step 0 above).
  3. Decide your OtaKit lane model up front: which named channels, and whether runtimeVersion starts now (recommended) or at your next native break.
  4. Swap the plugin: remove @capgo/capacitor-updater or @capawesome/capacitor-live-update, then npm install @otakit/capacitor-updater, update capacitor.config.ts, and sync native projects.
  5. Wire notifyAppReady() into your real app-ready path — after your app shell renders, not before. This is the rollback safety net; don't ship without it.
  6. Verify the full cycle on a test channel: release, download, activation, a deliberate broken build to watch automatic rollback, and a runtimeVersion mismatch to confirm lane isolation.
  7. Ship the new store build containing OtaKit.
  8. Keep the old vendor account alive while old binaries age out — they still need it for any emergency fix you push during the window.
  9. Watch adoption in the OtaKit dashboard; when the old binaries are down to a tail you can live with, retire the old channels and the old subscription.

Day-to-day releases after the switch

npm run build
otakit upload --release              # base channel

otakit upload --release staging      # named channel
otakit release <bundle-id> --channel production   # promote the same bundle

otakit upload --release --force-immediate         # emergency fix
otakit upload --release --strategy deltas         # delta delivery

Questions mid-migration? The setup guide and update strategies cover the details, and we're happy to help with gnarly routing models at support@otakit.app — we've probably seen yours.

Related docs