The iOS 27 SDK becomes mandatory in April 2027
Apple confirmed that App Store uploads must use the iOS 27 SDK from April 2027. That means Xcode 27, an Apple silicon Mac and a UIScene migration. A timeline and plan for Capacitor apps.
On 9 September, when App Store submissions opened for iOS 27, Apple also set the next deadline: starting April 2027, iOS and iPadOS apps uploaded to App Store Connect must be built with the iOS 27 SDK or later. In the same notice, Apple confirmed that macOS 27 is Apple silicon only.
For a Capacitor app, those two lines add up to three pieces of work: Xcode 27, an Apple silicon Mac to run it, and the UIScene migration that the iOS 27 SDK requires. None is hard. All three are easier in October than in March.
What the requirement means
The rule is about how the app is built, not which iOS versions it supports. You can keep your deployment target where it is and still support older iPhones. What changes is that after April 2027, you cannot upload a new build made with Xcode 26.
Apps already on the App Store are not removed. The deadline hits the next time you need to ship a native update, which is often at the worst moment: a store-mandated fix, a plugin security patch, a payment SDK update.
Three things you need
| Requirement | Why | What to do |
|---|---|---|
| Xcode 27 | The iOS 27 SDK ships with Xcode 27 | Install Xcode 27 alongside Xcode 26 and build a branch with it |
| An Apple silicon Mac | Current Xcode releases run only on Apple silicon; macOS 27 drops Intel entirely | Replace Intel build machines, or use Apple silicon CI runners or a cloud Mac |
| UIScene lifecycle | Apps built against the iOS 27 SDK must adopt scenes | Upgrade to Capacitor 8.5 and run npx cap migrate |
The UIScene migration
This is the only code change, and for most apps it is small. Capacitor 8.5 moves the iOS template to the scene-based lifecycle:
npm install @capacitor/core@^8.5 @capacitor/cli@^8.5 @capacitor/ios@^8.5 npx cap migrate npx cap sync ios
Where it goes wrong is custom code in AppDelegate.swift that expects app-level callbacks for URLs, window setup or state restoration, and older plugins that assume a single UIWindow. We covered the details in iOS 27, UIScene and Capacitor.
If your build machine is an Intel Mac
This is where teams get caught. An Intel Mac that builds your app fine today cannot run the Xcode that the April deadline needs. Options, roughly by effort:
- CI with Apple silicon runners. GitHub Actions, Codemagic, Bitrise and Xcode Cloud all offer them. See the best CI/CD platforms for Capacitor.
- A cloud Mac by the hour for the occasional native build and debugging session. See building a Capacitor iOS app on a cloud Mac.
- A new Mac. Worth it if you do native work every week.
A timeline that avoids the rush
| When | Step |
|---|---|
| October 2026 | Upgrade to Capacitor 8.5, build with Xcode 27 on a branch, fix plugin issues |
| November 2026 | Ship the Xcode 27 build to TestFlight and then to the App Store |
| Late November 2026 | Capacitor 9 is expected; plan it as a separate release |
| Early 2027 | Retire Intel build machines and Xcode 26 from CI |
| April 2027 | Deadline: nothing to do if the steps above are done |
Ship the migration early for one practical reason: after the new native shell is live, you can keep shipping everything else as web updates, without touching Xcode again until the next native change.
Keep OTA bundles matched to the new shell
A native release that changes Capacitor's version or your plugin set is a new runtime. Old web bundles built for the previous shell should not be applied to it, and new bundles that rely on the new shell should not reach users still on the old one.
With OtaKit, bump runtimeVersion in the plugin config in the same commit as the Capacitor 8.5 upgrade:
plugins: {
OtaKit: {
appId: 'YOUR_OTAKIT_APP_ID',
runtimeVersion: '2026.10',
},
},Users on the old store build keep receiving bundles for the old runtime. Users who update from the App Store move to the new lane on first launch. The CLI also checks native plugin compatibility on upload, so a bundle that needs a newer shell does not reach one that cannot run it. See channels and runtime version.