For most of Flutter's life, every iOS build has quietly depended on CocoaPods: a Ruby tool, a Podfile, a Pods/ directory and a workspace that Xcode only half understood. That era is ending. Starting with Flutter 3.44, Swift Package Manager (SwiftPM) is the default dependency manager for iOS and macOS apps, and Flutter only falls back to CocoaPods for plugins that don't support SwiftPM yet.
The deadline that makes this urgent is not Flutter's: according to the Flutter documentation, the CocoaPods trunk becomes permanently read-only on December 2, 2026. Existing pods will still install, but no new version of any pod can be published after that date. A plugin that still depends on a pod is effectively frozen on whatever native SDK version it had on that day.
What actually changes in your project
When you upgrade to Flutter 3.44 or later and run or build the iOS app, the Flutter CLI edits your Xcode project for you. Two things are added:
- A generated Swift package (
FlutterGeneratedPluginSwiftPackage) that pulls in every plugin that ships aPackage.swift. It becomes a target dependency ofRunner. - A scheme pre-action called Run Prepare Flutter Framework Script, which prepares the Flutter framework before Xcode resolves packages.
Plugins without SwiftPM support keep going through CocoaPods, so for a while most real projects run in a mixed mode: some native dependencies come from Swift packages, the rest from the Podfile. That's expected. The goal is to shrink the CocoaPods side to zero, not to rip it out on day one.
You can turn SwiftPM off per project through the config section of pubspec.yaml, or globally with flutter config --no-enable-swift-package-manager. Treat that as an emergency switch, not a plan. Flutter's docs say disabling it won't be allowed in the future, and disabling doesn't remove the integration from your Xcode project anyway.
Audit your plugins first
The single most useful hour you can spend is listing every plugin in pubspec.lock that has an iOS implementation and checking which ones ship a Package.swift in their ios/ or darwin/ folder. Sort them into three buckets:
- Already SwiftPM-ready. Nothing to do except keep them updated.
- Maintained, but not migrated yet. Check the issue tracker. Most popular plugins have an open issue or PR; upvote it or contribute it.
- Unmaintained. This is the real risk. After December 2, the native SDK behind the plugin can't be updated through CocoaPods. Plan to fork it, replace it, or move the native part into your own code.
Pay special attention to plugins that wrap large vendor SDKs such as maps, payments, analytics, video and Bluetooth stacks. Many vendors already publish their iOS SDKs as Swift packages, and the plugin needs to switch to that distribution, not just add a manifest.
Things that break in real projects
Flavors and custom schemes. The pre-action is added to the Runner scheme. If you build with flavors (Runner-dev, Runner-prod and so on), confirm each scheme has the pre-action too. A missing pre-action shows up as confusing "module not found" errors that only happen for one flavor.
Hand-edited Xcode projects. Old projects often carry manual build phases, copied frameworks or linker flags added years ago to make a pod work. These can conflict with the generated package. If the automatic migration fails, Flutter's docs ask you to file an issue with your project.pbxproj and scheme file, which is also a good signal that the project needs cleanup.
Duplicate native symbols. In mixed mode, the same native library can arrive twice: once as a Swift package through one plugin and once as a pod through another. The symptom is duplicate-symbol link errors or, worse, two copies of a singleton at runtime. Align versions or migrate both plugins together.
Privacy manifests and resources. Swift packages carry their own resources, including privacy manifests. Check that bundled resources such as images, fonts and PrivacyInfo.xcprivacy still end up in the built app after the switch, especially for plugins that used CocoaPods resource bundles.
CI and build caching
Your CI cache keys probably reference Podfile.lock and the Pods/ directory. With SwiftPM, resolved packages live in Xcode's SourcePackages directory and the lock file is Package.resolved. Update the cache keys, or every build will resolve packages from scratch and your iOS pipeline will quietly get slower. Commit Package.resolved the same way you committed Podfile.lock, so every machine builds the same native versions.
If your pipeline installs Ruby only for CocoaPods, keep it until the last pod is gone, then delete it. A faster, simpler CI image is one of the nicer side effects of this migration.
If you maintain a plugin
Flutter's guidance for plugin authors is to support both SwiftPM and CocoaPods until further notice. In practice that means adding a Package.swift next to the existing .podspec, moving sources into the layout SwiftPM expects, and testing the example app with SwiftPM both on and off. Ship that before December, because apps that upgrade Flutter will start reporting your plugin as the thing blocking their migration.
A migration order that works
- Upgrade Flutter on a branch and let the CLI migrate the Xcode project.
- Build every flavor and configuration, on a clean machine or CI, not only on your laptop.
- Audit plugins and open issues or PRs for the ones still on CocoaPods.
- Replace or fork anything unmaintained.
- Fix CI caching and commit
Package.resolved. - When the last pod is gone, remove the CocoaPods integration completely.
The Flutter team has done most of the mechanical work. What's left is dependency hygiene, and the CocoaPods freeze turns that from "someday" into a date on the calendar.
I build and maintain Flutter apps for iOS and Android. If your project needs a hand with this migration, see Flutter app development or get in touch.