A Flutter stable release index currently lists Flutter 3.47 as a stable version, while Flutter’s official documentation says Swift Package Manager became the default native dependency path from Flutter 3.44 onward (Flutter release notes, Flutter 3.44 announcement). Do not upgrade the production build node in place. This week, clone the pipeline onto an isolated remote Mac, keep a CocoaPods fallback for plugins that need it, and switch the main node only after device builds, signing, Archive, export, and rollback tests pass.

This guide is for Flutter developers maintaining native iOS plugins, flavors, custom targets, or add-to-app projects. It also targets DevOps engineers responsible for reproducible remote Mac build nodes and iOS CI recovery.

Last updated August 21, 2026. Version status and dependency policy were checked against the Flutter stable release index, the Swift Package Manager application guide, Flutter’s add-to-app documentation, and Apple’s distribution documentation.

01

Why the migration must be isolated

A Flutter 3.47 iOS upgrade is not only a framework version change. It can also change how native dependencies are resolved, how generated project files are updated, and which build steps are visible to Xcode.

Three risks deserve attention before any file is edited:

  • Dependency resolution can become mixed. Flutter supports Swift Package Manager as the default path for supported native dependencies, while dependencies that do not support it can still use CocoaPods as a fallback. A remaining Podfile is therefore not proof that migration failed.
  • A successful Debug build proves too little. A default Runner scheme may compile while a staging flavor, production Archive, custom target, or signed export fails later.
  • Remote sessions hide environment gaps. A build may work inside a graphical shell but fail over SSH because PATH, signing credentials, keychain access, or cache directories are different.
  • Generated project changes can be difficult to reverse. If project files, package references, lockfiles, and scripts are committed together without a baseline, it becomes unclear which change caused the failure.
  • Production rollback is slower than source rollback. A Git revert does not automatically restore installed SDKs, package caches, keychain state, or generated Xcode metadata on a persistent build host.

Before migration, preserve the current Flutter version declaration, dependency lockfiles, Podfile, Podfile.lock where present, Xcode project and workspace files, shared schemes, export configuration, signing configuration, CI scripts, and a successful build record. The record should include the exact command, scheme, configuration, artifact path, signing result, and commit identifier.

02

First step: classify the project before changing dependencies

The migration path depends on the project shape, not on whether the repository contains CocoaPods files. Use the following classification before running an upgrade command.

Project condition What to inspect first Safe initial direction
Standard Flutter iOS app with limited native customization Generated project changes, package references, schemes, and build scripts Test the default Swift Package Manager path in isolation
App with native plugins still tied to CocoaPods Dependency source per plugin, lock resolution, compiler output, and minimum system requirements Continue with a mixed setup until each plugin has a verified alternative
Multiple flavors, custom targets, or custom configurations Scheme-to-configuration mapping, package association, scripts, and export settings Validate every build configuration separately
Flutter add-to-app integration Host project dependencies, module initialization, resources, and existing framework or pod integration Follow the host-project migration path; do not stack integrations blindly
Remote Mac or CI node SDK locks, environment variables, credentials, caches, and reboot recovery Build on a disposable or isolated node before changing the main node

Does a Flutter 3.47 upgrade require every iOS project to move immediately to Swift Package Manager?
No. Flutter’s documented policy makes Swift Package Manager the default for supported iOS and macOS native dependencies from Flutter 3.44, but unsupported dependencies can retain a CocoaPods fallback. The correct question is whether each dependency used by the project resolves, compiles, signs, and behaves correctly under the selected path.

The project should record, plugin by plugin:

  1. The dependency declaration and source.
  2. The resolver that actually handled it.
  3. The resolved version or lock state.
  4. The native build product generated.
  5. The minimum operating-system requirement.
  6. Whether Debug, device Release, Archive, and export were tested.

This prevents a common error: deleting the Podfile merely because the new Flutter tooling supports Swift Package Manager.

03

Standard apps should compare generated changes, not copy settings by hand

For a project close to the default Flutter structure, the safest migration is observational. Update the Flutter SDK in a separate branch or clone, regenerate only through the supported Flutter workflow, and inspect the resulting Xcode changes rather than manually copying settings from another project.

A controlled sequence can look like this:

git checkout -b flutter-3-47-ios-migration

flutter --version
flutter pub get
flutter doctor -v

git status --short
git diff -- ios

The commands themselves are not acceptance evidence. The useful evidence is the resulting diff and the build matrix. Review whether the change adds or modifies Swift package references, Flutter framework preparation scripts, build phases, generated configuration, workspace metadata, or shared schemes.

The standard project should pass this loop:

  1. Resolve Dart and native dependencies from a clean checkout.
  2. Open the intended Xcode workspace or project and inspect package resolution.
  3. Run the iOS simulator build.
  4. Build a physical-device configuration.
  5. Create an unsigned or signing-controlled Release build.
  6. Create an Archive and export it using the project’s normal distribution settings.
  7. Compare the resulting project and generated-file changes with the migration baseline.

What should be committed after the default migration?
Commit files that are part of the project’s reproducible build definition, such as intentional Xcode project changes, shared schemes, dependency lockfiles, and CI configuration. Do not commit transient user state or machine-specific metadata unless the existing repository policy explicitly requires it. The boundary must be reviewed as a diff, not inferred from a successful local build.

04

Mixed CocoaPods and Swift Package Manager projects need a plugin ledger

A mixed dependency state is often the correct temporary state. Flutter’s official Swift Package Manager guide should be used to verify the supported integration model, while the project itself must establish whether each plugin works under the selected configuration.

A practical plugin ledger can contain these columns:

Plugin | Source | Resolver | Lock state | Native product | Minimum OS | Debug | Device | Archive | Decision

The decision should be one of three outcomes:

  • Keep dual-track: the plugin is required, CocoaPods resolves reliably, and the project passes the full build matrix.
  • Replace or migrate: an equivalent supported integration is available and has passed device, Archive, signing, and export checks.
  • Hold the Flutter upgrade: the plugin is business-critical, its native behavior is unverified, and no reliable rollback has been rehearsed.

Can an existing Flutter iOS project continue using CocoaPods?
It can continue using CocoaPods for dependencies that still require that path, provided the project’s resolver state and build outputs remain reproducible. The presence of CocoaPods should be treated as a dependency fact to document, not as an automatic defect.

Avoid declaring compatibility from a plugin name alone. The relevant evidence is the native compiler output, package or pod resolution, target linkage, runtime initialization, and distribution build. Community reports can identify areas to investigate, but they should not replace a project-specific acceptance run.

Migration warning: never remove the old dependency integration and add the new integration in the same unverified edit. First establish a passing baseline, then test one integration path, then rehearse restoration of the previous path.

05

Multi-flavor projects need configuration-level acceptance

Flavors and custom targets multiply the places where an upgrade can fail. The default Runner Debug scheme is only one path through the project. A staging configuration may use a different bundle identifier, signing identity, resource set, package association, or script input.

For each flavor or target, verify:

  • The scheme is shared when CI needs to use it.
  • The scheme maps to the intended Build Configuration.
  • Swift package dependencies are available to the correct target.
  • CocoaPods targets, if retained, link to the intended configuration.
  • Flutter preparation scripts run for every required configuration.
  • Bundle identifiers, entitlements, provisioning settings, and export options match the target.
  • The artifact path is unique enough for CI to avoid overwriting another flavor.
  • Tests and Archive use the same configuration assumptions as the release workflow.

How should a multi-flavor Flutter project validate Swift Package Manager?
It should resolve and build each flavor through its own shared scheme and configuration. A package visible in the default application target may not be correctly linked to a custom target. Run the project’s normal test, device build, Archive, and export tasks for staging and production rather than treating one successful Debug run as coverage.

A useful CI record includes the scheme name, configuration name, dependency resolution output, build log, Archive location, export location, and signing result. If production Archive fails while staging passes, the migration is not ready for the main node.

06

Add-to-app requires a separate host-project review

A Flutter add-to-app project has a different ownership boundary. The native iOS host controls dependency integration, application startup, resources, targets, and often more than one build configuration. A pure Flutter application’s generated project assumptions cannot be copied into that host without inspection.

The review should cover:

  1. The host project’s existing package and pod dependencies.
  2. Flutter module initialization and the code path that starts the Flutter view.
  3. Framework or package references already used by the host.
  4. Resource bundles, asset handling, and generated Flutter artifacts.
  5. Debug, test, Release, and distribution configurations.
  6. The interaction between the host’s targets and the new dependency mechanism.

What changes in a Flutter add-to-app migration?
The host application remains the source of truth for native integration. Existing framework or CocoaPods wiring must be examined before introducing a Swift Package Manager path. Adding both paths without proving ownership can create duplicate products, conflicting linker inputs, or a host target that launches in Debug but fails in Release.

At minimum, acceptance should include one native screen launching the Flutter module, one Release build, one signed distribution build, and one restoration exercise that returns the host project to its previous integration. The Flutter add-to-app project setup guide provides the official maintenance context, but the host repository still needs its own evidence.

07

Remote Mac acceptance must reproduce the CI session

A remote Mac is useful for migration because it can preserve an isolated build environment without requiring the developer’s primary workstation to become the test host. It is not safe merely because it has macOS. The node must reproduce the same non-interactive conditions used by CI.

Check these areas before assigning it as the main builder:

  • Flutter SDK version and channel are explicit.
  • Xcode selection is explicit and recorded.
  • Dependency lockfiles are restored from source control.
  • SSH sessions receive the expected PATH, locale, shell profile, and environment variables.
  • Signing certificates and provisioning access are available to the intended user or CI identity.
  • Keychain access works without an open graphical session where the pipeline requires non-interactive operation.
  • Cache directories are isolated by project and can be cleared.
  • A reboot does not erase required configuration or leave the node unable to build.
  • Archive and export commands produce artifacts at known paths.
  • The old node or old integration can still be restored.

Run both clean and cached builds. A clean build tests reproducibility. A cached build tests whether stale package or generated artifacts hide a migration problem. Then run the same signing and export task used by the release pipeline.

How should a remote Mac be accepted for Flutter iOS Archive and signing work?
Acceptance requires a real project, not a sample app. The node should complete dependency resolution, simulator or device compilation, Archive, signing, export, and a restart recovery test through the same SSH or CI session type used in production.

Apple’s documentation on Xcode distribution workflows, Archive and export, and sharing signing certificates should be used to verify the distribution and credential handling steps. These documents define the platform workflow; they do not prove that a particular remote node has the correct project permissions.

08

Apply explicit go, hold, and rollback conditions

The migration decision should be made from evidence rather than schedule pressure.

  • Choose the isolated upgrade path if the project has a reproducible baseline, the dependency ledger is complete, and every required flavor or target can be tested on a separate node.
  • Keep the mixed resolver path if one or more required plugins still depend on CocoaPods but compile and distribute successfully through the tested pipeline.
  • Delay the upgrade if a critical plugin has unresolved native errors, if production signing is not reproducible, or if add-to-app ownership is unclear.
  • Return to the previous integration if the new path changes runtime behavior, breaks a production configuration, or cannot reproduce the former artifact.
  • Switch the production node only after the isolated node passes clean build, cached build, device signing, Archive, export, and reboot recovery.

A rollback plan should restore more than source files. Preserve the previous SDK state, dependency locks, Xcode project changes, CI variables, keychain access instructions, cache policy, and node image or setup procedure. The rollback test is complete only when the former pipeline can produce its expected artifact again.

09

Migration decision matrix

Use this matrix immediately before changing the production builder. It is deliberately condition-based: each failed condition has a defined fallback.

If the project has… And the evidence shows… Select…
Standard Flutter structure Swift Package Manager resolves, device build passes, and Archive exports correctly Proceed on the isolated node, then schedule production cutover
Required CocoaPods plugins Pods resolve consistently and all distribution checks pass Retain the mixed setup and document the plugin boundary
A plugin with unresolved native compatibility Compiler, runtime, or Archive behavior is not verified Hold the upgrade or replace the plugin
Multiple flavors or custom targets Every shared scheme maps correctly and each release path passes Proceed only after configuration-level evidence is saved
Add-to-app host integration Native launch, Release, signing, and restoration tests pass Approve the host migration
Remote CI node SSH, credentials, caches, Archive, export, and reboot recovery pass Promote the isolated node after a controlled cutover
Any production failure without a rehearsed restore Previous artifact cannot be reproduced quickly Do not replace the production node
10

Recommended rollout plan for this week

Time window Action Exit evidence
Day 1 Freeze the current build record and clone the repository Baseline commit, logs, artifacts, and configuration inventory
Day 2 Prepare an isolated remote Mac and lock Flutter, Xcode, dependencies, and credentials Non-interactive environment report
Day 3 Test the standard app or the smallest representative project Dependency diff and simulator or device result
Day 4 Test plugins, flavors, custom targets, or add-to-app paths Per-configuration logs and artifact records
Day 5 Run clean, cached, signed Archive, export, and reboot recovery tests Complete acceptance record
Cutover window Switch the CI label or builder assignment First production job and rollback checkpoint

If the team has no spare Mac for isolation, a separate remote Mac development environment can serve as a temporary migration node. NodeMini provides remote access to a hosted Mac, but the engineering decision should remain conditional: use it for a controlled release-cycle test, reproduce the current pipeline, and release or retain the node only after the acceptance record is complete. For teams that need a dedicated build machine rather than a short test window, review the available Mac rental configurations against the project’s access, signing, and uptime requirements.

A production node should not be replaced because Flutter 3.47 is available. It should be replaced when the project’s dependency path, every release configuration, signing flow, remote session, cache policy, and rollback procedure have passed on an isolated remote Mac. That approach keeps CocoaPods available where it is still required, adopts Swift Package Manager where the project can prove it, and turns the upgrade into a controlled engineering change rather than a recovery exercise.