If you already have a working web app, is the fastest path to mobile wrapping it with Capacitor 8, or writing native from scratch? This decision requires weighing cost against performance ceiling, especially for teams facing the move web app to mobile Capacitor 8 question in 2026. This article gives you a concrete, sourced decision framework — from what Capacitor 8.5 brings to App Store acceptance risk.
💡 Pro Tip: Before moving to Capacitor, test your existing web app's DOM-heavy animations and large list renders on a real device (not a simulator) — the WebView performance ceiling doesn't show up in a simulator.
Table of Contents
- Decision framework: which product fits Capacitor
- What Capacitor 8.5 brought (UIScene, TypeScript 7)
- The real cost: wrapping vs. rewriting
- Where the performance ceiling starts
- Access to native features (plugin ecosystem)
- App Store / Play acceptance risk
- Exit strategy: how to move to native later
- Decision table
- FAQ
- How do I move my web app to mobile with Capacitor?
- What changed in Capacitor 8?
- Does a Capacitor app get accepted to the App Store?
- Is Capacitor or from-scratch native cheaper?
- Update (September 2026)
- Conclusion
- Sources
Decision framework: which product fits Capacitor
Capacitor's own documentation defines it as a runtime that creates "Web Native apps": it stays as close to web standards as possible while offering full access to native SDKs when needed. The official statement is clear: "If it works in the browser, it probably works in a mobile app when using Capacitor." This definition is also the foundation of the decision framework.
In practice, ask yourself this: is the app's core value _content and interaction_, or _performance and fluidity_? For content-heavy, form-heavy, CRUD-heavy products (dashboards, admin panels, catalogs, booking flows), moving your web team's existing codebase to mobile via Capacitor is a sound engineering call. For products needing heavy animation, game logic, AR/camera-heavy or frame-sensitive interaction, the WebView's render layer may fall behind native — worth comparing native or a solution with its own render engine like Flutter (see Flutter vs SwiftUI comparison).
The second question is team composition: a web team that knows React/Vue/Svelte but not Swift/Kotlin can still add native capability through Capacitor's plugin API (detailed below) — without spinning up a native team from scratch.
The third question is time horizon: is the priority a quick MVP in the stores, or the right long-term architecture from day one? Capacitor's "your web code runs almost as-is" positioning is a natural MVP-speed advantage, but not an unlimited one. As the product matures and its user base grows, where the performance ceiling starts (next section) matters more. Treat this as a revisitable decision tied to the roadmap, not a one-time choice.
What Capacitor 8.5 brought (UIScene, TypeScript 7)
Capacitor 8.5.0 was released on July 31, 2026. Three changes in the release notes directly affect this decision:
- iOS UIScene support — listed in the official changelog as "ios: UIScene Support (#8536)." Preparation for the requirement Apple announced at WWDC25: per Flutter's documentation, starting with the release following iOS 26, every UIKit app compiled with the latest SDK will need to use the UIScene lifecycle, or the app won't launch.
- CLI migrator — a CLI tool was added to ease the transition, via "cli: add migrator functionality for adopting UIScene (#8544)."
- TypeScript 7 support — the CLI now supports TypeScript 7 when loading
capacitor.config.ts("cli: support TypeScript 7 when loading capacitor.config.ts (#8534)"), listed in the official changelog as a bug fix rather than a feature.
In practice, this means capacitor.config.ts now loads cleanly with the newer TS compiler, with no need to rework your type definitions.
1// capacitor.config.ts — a typical structure that loads cleanly with TypeScript 72import type { CapacitorConfig } from "@capacitor/cli";3 4const config: CapacitorConfig = {5 appId: "com.example.webapp",6 appName: "Example Web App",7 webDir: "dist",8 server: {9 androidScheme: "https",10 },11};12 13export default config;Critical point: Capacitor's core library still supports the legacy AppDelegate path. The official 8.5 upgrade guide says this explicitly: "The core library still supports the AppDelegate path, so updating the dependency alone won't break your app. To build with Xcode 27, though, your app project needs to adopt the scene lifecycle."
1{2 "dependencies": {3 "@capacitor/core": "^8.5.0",4 "@capacitor/cli": "^8.5.0",5 "@capacitor/ios": "^8.5.0",6 "@capacitor/android": "^8.5.0"7 }8}The real cost: wrapping vs. rewriting
Per the 8.5 upgrade guide, adopting UIScene concretely comes down to three parts: "one new file, one Info.plist entry, and one method in your AppDelegate" — namely SceneDelegate.swift, one Info.plist entry, and one AppDelegate method. That's concrete evidence of how low the integration cost is for "wrapping" an existing web app with Capacitor; there's no exact "N days" figure in official sources, so calibrate the time estimate against your own team's experience.
1<key>UIApplicationSceneManifest</key>2<dict>3 <key>UIApplicationSupportsMultipleScenes</key>4 <false/>5 <key>UISceneConfigurations</key>6 <dict>7 <key>UIWindowSceneSessionRoleApplication</key>8 <array>9 <dict>10 <key>UISceneConfigurationName</key>11 <string>Default Configuration</string>12 <key>UISceneDelegateClassName</key>13 <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>14 <key>UISceneStoryboardFile</key>15 <string>Main</string>16 </dict>17 </array>18 </dict>19</dict>The AppDelegate method that completes this step (from the official guide):
1func application(_ application: UIApplication,2 configurationForConnecting connectingSceneSession: UISceneSession,3 options: UIScene.ConnectionOptions) -> UISceneConfiguration {4 let config = UISceneConfiguration(name: "Default Configuration",5 sessionRole: connectingSceneSession.role)6 config.delegateClass = SceneDelegate.self7 return config8}A from-scratch native rewrite, by contrast, brings its own costs: two separate codebases (iOS + Android) to maintain, porting existing web business logic to native, and a new team skillset (Swift + Kotlin). These three items explain why "wrap" is the relatively lower-risk starting point — but they also determine the final performance ceiling (next section).
1# Adding Capacitor to an existing web project (real CLI commands)2npm install @capacitor/core@^8.5.0 @capacitor/cli@^8.5.03npx cap init4npm install @capacitor/ios@^8.5.0 @capacitor/android@^8.5.05npx cap add ios6npx cap add android7npx cap sync iosThere's no extra "scene lifecycle" step on the Android side — this change is iOS-specific only. A separate command is enough to sync the Android build:
1# Sync on the Android side (no UIScene step required)2npx cap sync android3npx cap open androidWhere the performance ceiling starts
Capacitor's WebView-based architecture sits on a different layer than native UI kits' render pipeline (UIKit/SwiftUI, Jetpack Compose). That cost can go unnoticed in most everyday scenarios (form filling, list scrolling, page transitions); but where 60fps is critical — continuous animation, complex canvas rendering, real-time camera processing — the WebView's render layer may fall behind native engines.
No official source quantifies this ceiling numerically, so a precise "don't use Capacitor below this FPS" threshold would be misleading. The practical approach: test critical screens (e.g. the main flow, an onboarding animation) on a real device early in a prototype, and measure the ceiling against your own requirements. If heavy visual interaction is your product's center of gravity, solutions with their own GPU layer, like Flutter's Impeller render engine, are worth comparing.
Don't settle for a single device: WebView behavior can differ between iOS and Android, even between older and newer versions of the same platform. Testing on both a low-to-mid-range Android device and an iPhone that's a few years old gives a realistic answer specific to your product — since there's no general official threshold, this measurement responsibility falls on the team.
Access to native features (plugin ecosystem)
Capacitor's native access model relies on three languages: the official documentation says "a Plugin API for Swift on iOS, Java on Android, and JavaScript for the web." So if a native feature is missing (say, a specific sensor API), you can write your own Swift/Java plugin and call it from JavaScript — this effectively removes the "wrap" approach's ceiling on native access, though writing a plugin requires native knowledge.
Version 8.5 also proves this ecosystem keeps evolving: new scene-lifecycle-specific notifications were added (.capacitorSceneWillConnect, .capacitorSceneOpenURL, .capacitorSceneOpenUniversalLink), but the official guide warns: "Note they're only posted on 8.5 and later, so plugins that also support earlier Capacitor 8 versions should stay on the legacy notifications."
The same version also removed some legacy APIs: "Removed: TmpViewController and the long-deprecated CapacitorBridge.tmpWindow property and tmpViewControllerAppeared notification." This shows plugin maintenance is an ongoing job requiring release tracking — not "wrap once and forget."
In practice: if your project uses community plugins (such as camera, push notifications, biometric auth), check those plugins' changelogs before every Capacitor major/minor upgrade. Official core plugins (under @capacitor/core) generally update in sync with the main release, but third-party plugins' update cadence varies — one of the "wrap" approach's hidden maintenance costs.
1// SceneDelegate.swift from the official 8.5 upgrade guide — the same file2// works in both SPM and CocoaPods projects; Capacitor's migrator3// generates it automatically4import UIKit5import Capacitor6 7class SceneDelegate: UIResponder, UIWindowSceneDelegate {8 var window: UIWindow?9 10 func scene(_ scene: UIScene, willConnectTo session: UISceneSession,11 options connectionOptions: UIScene.ConnectionOptions) {12 guard let windowScene = scene as? UIWindowScene else { return }13 window = UIWindow(windowScene: windowScene)14 window?.rootViewController = CAPBridgeViewController()15 window?.makeKeyAndVisible()16 SceneDelegateProxy.shared.scene(scene, willConnectTo: session, options: connectionOptions)17 }18 19 func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {20 SceneDelegateProxy.shared.scene(scene, openURLContexts: URLContexts)21 }22 23 func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {24 SceneDelegateProxy.shared.scene(scene, continue: userActivity)25 }26}App Store / Play acceptance risk
There's no official source from Apple or Google publishing a numeric rejection rate specific to Capacitor or hybrid apps — so giving a percentage here would be fabrication. What's known: Capacitor's own positioning rests on the principle "if it works in the browser, it probably also works on mobile," meaning the app goes through store review like a native app — there's no separate "hybrid" category or special approval process.
The practical risk comes from the stores' general principle: an app shouldn't be perceived as a "WebView wrapper" that merely displays a website without adding native value. The concrete counterpart to this is using Capacitor's plugin ecosystem (push notifications, biometric authentication, native sharing, etc.) to add real native interaction to the app — not just showing a web page like an <iframe>.
Exit strategy: how to move to native later
There's no official "exiting Capacitor for native" document, but the architecture itself is designed to make this transition easier: Capacitor's plugin API already requires you to write native code in Swift/Java. In practice, a gradual transition works like this: first rewrite a single performance-critical screen (e.g. the main flow animation) as a native plugin or a native subview, and leave the rest in the WebView. As the proportion of native screens grows over time, you end up with a lower-risk, piece-by-piece path to a full native rewrite. This strategy isn't based on an official source — read it as an editorial suggestion, to be adapted to your team's risk tolerance.
The biggest advantage of this gradual transition is that it removes the "all or nothing" decision. When a team starts with Capacitor and moves a specific screen to native, it's not a problem for the rest of the app to stay WebView-based — Capacitor's plugin architecture already allows web and native code to coexist within the same app. This makes the "exit fast first, move critical screens to native later" strategy technically workable.
Decision table
Criterion | Capacitor 8.5 (wrap) | Native rewrite |
|---|---|---|
Integration cost | Low — 1 file + 1 Info.plist entry + 1 AppDelegate method added to existing web code (for UIScene) | High — two separate codebases, written from scratch |
Team skillset | Existing web team + Swift/Java when native plugins are needed | Swift (iOS) + Kotlin (Android) expertise required |
Access to native features | Via Plugin API (Swift/Java/JS) — comprehensive but requires writing code | Direct, no intermediary |
Performance ceiling | WebView render layer — editorial caution needed for heavy animation/AR (no official benchmark) | Native render engine — higher upper bound |
Store acceptance | General principle: treated like native if it has real native interaction (no official statistics) | Standard native review process |
Version tracking | Requires tracking plugin compatibility across 8.x → 9.x transitions (see 8.5 notification change) | Requires tracking platform SDK updates |
FAQ
How do I move my web app to mobile with Capacitor?
You install the @capacitor/core and @capacitor/cli packages into your existing web project and initialize with npx cap init, then create the platform projects with npx cap add ios / npx cap add android and copy your web build into the native project with npx cap sync. For builds after iOS 26, adopting UIScene (a SceneDelegate file + an Info.plist entry + an AppDelegate method) is required.
What changed in Capacitor 8?
Capacitor 8.5.0 (July 31, 2026) brought UIScene support for iOS along with a CLI migrator that eases the transition, and also added TypeScript 7 support when loading capacitor.config.ts.
Does a Capacitor app get accepted to the App Store?
There's no separate approval category or published rejection-rate statistic from Apple or Google specific to Capacitor. Capacitor's own positioning rests on the principle that an app that works in the browser will also work on mobile; having the app include real native interaction (push, biometric, native sharing, etc.) reduces the risk of it being perceived as a mere web page wrapper.
Is Capacitor or from-scratch native cheaper?
There's no exact cost figure in official sources. But the technical evidence shows: moving to Capacitor (including UIScene) requires small, targeted additions to existing code, while a from-scratch native rewrite requires building two separate codebases from the ground up — this asymmetry generally makes Capacitor the lower initial-cost option for teams with an existing web codebase.
GOLDEN TIP
The most valuable insight in this article
This tip holds the article's most important takeaway.
Easter Egg
You found a hidden gem!
There's a hidden detail in this section. Want to uncover it?
Reader Reward
Here's the technical checklist to review before moving your web app to mobile with Capacitor 8. This list is a practical summary of the sourced information in the article, and can be used as a final check before the transition.
Update (September 2026)
Since this article was published on August 6, 2026, Capacitor has received two more patch releases: 8.5.1 and 8.5.2 (September 11, 2026). One of the changes in 8.5.2 is directly related to UIScene support: "ios: do not forward scene lifecycle events to the page before it has loaded (#8595)" — meaning scene lifecycle events are no longer forwarded to the JavaScript side before the page has fully loaded. This is concrete evidence that UIScene support from 8.5.0 continues to mature. Also, 9.0.0-alpha.7 was released on September 18, 2026, but it's still in alpha and shouldn't yet be treated as a reference for a production decision. If you want to keep this article current, we recommend checking the current latest tag (queryable on npm) before going to production.
Conclusion
Moving a web app to mobile with Capacitor 8 is a technically low-friction starting point if you already have a web codebase. Capacitor 8.5's UIScene support and migrator make it easier to prepare for the requirement Apple announced at WWDC25; but the final decision depends on your product's performance profile. For products requiring heavy animation or game-like interaction, it's worth also checking out the Flutter vs SwiftUI comparison or the React Native vs Flutter comparison. If you're coming from the Kotlin/Compose ecosystem, take a look at the Compose Multiplatform production example and the Kotlin Multiplatform 1.1 case study. If you need to set up subscription infrastructure on mobile, our RevenueCat cross-platform subscription guide and Mobile DevOps best practices for CI/CD setup cover the next steps of this transition.
Sources
- Capacitor 8.5.0 Release Notes — the official changelog entry for UIScene support, the CLI migrator, and TypeScript 7 support.
- Capacitor 8.5 Upgrade Guide — the official migration guide for SceneDelegate, the Info.plist entry, removed APIs, and scene-scoped notifications.
- Capacitor Documentation — Overview — the "Web Native apps" positioning and the Plugin API definition.
- Flutter Breaking Changes — General List — the official record listing UIScene adoption under "Released in Flutter 3.38."
- Flutter UISceneDelegate Adoption Guide — the page covering the automatic migration that became default in Flutter 3.41, and Apple's WWDC25 requirement announcement.
Tags
iOS Development News
Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.
We respect your privacy. You can unsubscribe at any time.

