Compliance11 min read

The ultimate guide to App Store-compliant OTA updates

Apple and Google allow over-the-air updates — within limits. Exactly where the compliance line sits, what you can and cannot ship OTA, and how to stay on the right side of guideline 2.5.2 with Capacitor.

The most common worry about over-the-air updates is also the most misunderstood: “will Apple reject my app for this?” The short answer is no — OTA updates to the web layer of a Capacitor app are explicitly allowed by both Apple and Google, and have been for years. The longer answer is that there's a line, it's clearly drawn, and staying on the right side of it is straightforward once you know where it is.

This is the practical guide to that line: what the stores actually say, what you can and cannot ship over the air, and how to keep your OtaKit setup compliant.

Mental model: a store release changes the native shell. An OTA update changes the web app running inside it. Apple and Google care about the shell; the web layer is yours to update.

What Apple actually says

The relevant rule is App Store Review Guideline 2.5.2, which requires that apps be self-contained and not download code that changes their features or functionality in ways that create a materially different experience from what App Review approved. Crucially, it carves out an explicit exception: code executed by Apple's built-in WebKit or JavaScriptCore is fine, as long as it doesn't provide store, payment, or other native capabilities that circumvent review.

A Capacitor app's web layer runs in exactly that WebKit web view. Updating your HTML, CSS, and JavaScript over the air is the sanctioned case, not a loophole — the same mechanism Ionic's Appflow, Capgo, Capawesome, and OtaKit all rely on. What you may not do is use OTA to add native functionality or fundamentally change what the app is.

What Google actually says

Google Play's Device and Network Abuse policy restricts apps from downloading executable code (dex, native code) that changes the app's behavior in ways that violate policy. Interpreted code — JavaScript running in a web view — is not the target. Bug fixes, UI changes, and content updates to your web layer are standard practice and stay within policy.

The line, in one table

Ship over the airRequires a store release
HTML / CSS / JavaScriptNew or updated native plugins
UI, layout, copy, contentChanged permissions or entitlements
Bug fixes in the web layerCapacitor runtime upgrades
Feature flags, config, A/B testsAnything touching ios/ or android/
Business logic in your JSNative SDKs (payments, auth providers)

Four rules that keep you compliant

  1. Never ship native code over the air. Plugins, permissions, and the runtime go through review. OtaKit checks dependencies at upload and warns on mismatches.
  2. Don't fundamentally change the app. Update and improve what App Review saw; don't use OTA to turn a notes app into a casino.
  3. Keep payments native. Don't use OTA to route around In-App Purchase or introduce a payment path review never saw.
  4. Version native compatibility. Bump runtimeVersion on store releases so bundles only reach shells that can run them — this keeps behavior consistent with what was reviewed.
# a normal, compliant release: your built web layer, nothing native
npm run build
otakit upload --release

If a change would require you to bump runtimeVersion, it's a store release — not an OTA update. That single rule keeps almost everyone on the right side of the line.

Why this is safe to rely on

OTA for the web layer isn't a gray-area trick that might stop working — it's the documented, intended behavior of hybrid frameworks, used by thousands of production apps. The stores draw the line at native capability precisely because updating interpreted web code inside their own web view is something they designed for. Stay in the web layer and you're building on solid ground.

Where to go next

For the primary-source deep dive with the exact guideline text, see are OTA updates allowed? App Store and Google Play rules explained. Then how OTA works shows the delivery flow end to end.

Related docs