Guides8 min read

Error handling and update UX for Capacitor live updates

A failed update should be invisible to users, not a broken app. How to handle live update errors gracefully in Capacitor — retries, silent rollback, and the UX patterns that keep users unaffected.

The best-handled update failure is one the user never notices. A dropped download, a bad bundle, a device that goes offline mid-check — none of these should ever leave someone staring at a broken screen. This guide covers the error-handling and UX patterns that make Capacitor live updates feel invisible, using OtaKit.

Design principle: an update failure should degrade to the app the user already had, never to a worse state. The old bundle keeps working; the new one simply doesn't apply yet.

The failure modes to handle

  • Download failed — network dropped or bundle too large to finish.
  • Verification failed — hash didn't match the signed manifest.
  • Boot failed — the new bundle crashed before notifyAppReady().

OtaKit handles all three conservatively by default: a bundle that doesn't download, doesn't verify, or doesn't signal ready never becomes the active bundle. Your job is to handle the UX around those events.

Listen and stay quiet

Subscribe to the lifecycle events, but resist the urge to surface most of them. A failed background download is not a user-facing error — it's a “try again later” for your code, not a dialog for the user:

import { OtaKit } from '@otakit/capacitor-plugin';

OtaKit.addListener('downloadFailed', (info) => {
  // log it, retry later — do NOT alert the user
  reportToAnalytics('ota_download_failed', info);
});

OtaKit.addListener('rollback', (info) => {
  // the app self-healed; record it, stay silent
  reportToAnalytics('ota_rollback', info);
});

Retry, don't nag

Downloads fail transiently all the time on mobile. The right response is a quiet retry on the next check or app resume, not a prompt. Because the device keeps its working bundle in the meantime, there's no urgency — the update simply lands whenever the network cooperates.

When to actually tell the user

There is one case for a visible prompt: a mandatory update the app can't run without (a breaking API change, say). Then a clear “update required” screen is correct — see forced and mandatory updates. For everything else, silence is the better UX. The tradeoffs of visible vs silent are in background vs foreground updates.

Inspect getLastFailure() when you need to differentiate causes in your logs — a persistent verification failure is a release problem you must fix; repeated download failures are a size/network problem you can fix with deltas.

Where to go next

See Events for the full listener set and monitoring OTA updates to turn these events into a health signal.

Related docs