Capacitor App: CocoaPods → SPM Migration
Migrate an existing Capacitor app project's iOS dependency management from CocoaPods to Swift Package Manager (SPM). The migration is destructive — the existing
folder is deleted and re-scaffolded, then user-authored files are restored from a temporary backup.
Prerequisites
| Requirement | Version |
|---|
| Capacitor | 6+ |
| Node.js | 18+ |
| Xcode | 15+ |
| CocoaPods | installed (only used for verification before deletion) |
The project must be a Capacitor
app (not a plugin) and must currently use CocoaPods (
exists).
Procedures
Step 1: Verify Project State
- Verify the project is a Capacitor app, not a plugin. Confirm lists in and the project root contains a file.
- Verify the project currently uses CocoaPods. The file must exist. If it does not exist and
ios/App/CapApp-SPM/Package.swift
exists, the project is already on SPM — abort and inform the user.
- Read version from ( or ). Confirm the major version is 6 or higher. If lower, abort and direct the user to the skill first.
- Verify the working tree is clean. Run — if the output is non-empty, instruct the user to commit or stash changes before proceeding. The migration is destructive and must be reversible via .
Step 2: Inventory Installed Capacitor Plugins
Read
and record every installed Capacitor plugin under
. Match these prefixes:
- (official plugins)
- and
- Any other package with in its array
Record each plugin's package name and installed version. This list is used in Step 8 to verify all plugins resolve under SPM after re-sync.
Step 3: Identify Files to Preserve
Build a checklist of files in
that contain user-authored content and must survive the re-scaffold. For each path below, check whether the file exists. Record only the paths that exist.
| Path (relative to project root) | Purpose |
|---|
| App permissions, URL schemes, bundle config, custom keys |
ios/App/App/AppDelegate.swift
| Custom app lifecycle code |
ios/App/App/SceneDelegate.swift
| Custom scene lifecycle code (if present) |
ios/App/App/Assets.xcassets/
| App icon, splash assets, color sets |
| LaunchScreen and Main storyboards |
ios/App/App/App.entitlements
| Push, App Groups, Associated Domains, Keychain Sharing |
ios/App/App/GoogleService-Info.plist
| Firebase configuration (if Firebase plugins are installed) |
| Build configuration overrides |
ios/App/App.xcodeproj/project.pbxproj
| Source for extracting signing values in Step 5 |
Additionally, scan
for
any other files beyond
and
. These are user-authored Swift sources (e.g., custom Notification Service Extensions, custom view controllers) and must also be preserved.
If the project contains a Notification Service Extension or other Xcode targets under
(e.g.,
ios/App/NotificationService/
), record those directories as well.
Step 4: Back Up Preserved Files
-
Create the backup directory:
bash
mkdir -p .spm-migration-backup
-
Add
to
to prevent accidental commits:
diff
# iOS files
+.spm-migration-backup/
-
Copy each file recorded in Step 3 into
, preserving the relative path. For example:
bash
mkdir -p .spm-migration-backup/ios/App/App
cp ios/App/App/Info.plist .spm-migration-backup/ios/App/App/Info.plist
cp ios/App/App/AppDelegate.swift .spm-migration-backup/ios/App/App/AppDelegate.swift
cp -R ios/App/App/Assets.xcassets .spm-migration-backup/ios/App/App/Assets.xcassets
cp -R ios/App/App/Base.lproj .spm-migration-backup/ios/App/App/Base.lproj
Repeat for every file/directory identified in Step 3. Use
for directories.
-
Always copy the full
ios/App/App.xcodeproj/project.pbxproj
to
.spm-migration-backup/ios/App/App.xcodeproj/project.pbxproj
, even though the file itself will not be restored. Step 5 reads it to extract signing values, and Step 11 may reference it for capability reconfiguration.
Step 5: Extract Signing & Bundle Configuration
Read
.spm-migration-backup/ios/App/App.xcodeproj/project.pbxproj
and extract the values of the following build settings from the
target's
blocks (both
and
). Search for each key and record the assigned value:
PRODUCT_BUNDLE_IDENTIFIER
- (e.g., or )
PROVISIONING_PROFILE_SPECIFIER
(only if )
- (only if )
IPHONEOS_DEPLOYMENT_TARGET
Also record the list of capabilities by reading
.spm-migration-backup/ios/App/App/App.entitlements
(if present). Note all top-level keys (e.g.,
,
com.apple.security.application-groups
,
com.apple.developer.associated-domains
).
Save the extracted values in memory for use in Step 9.
Step 6: Delete the Folder
Confirm with the user that the working tree is committed (re-run
if necessary) and that
contains every file from Step 3. Then run:
Step 7: Re-scaffold iOS with SPM
Run:
bash
npx cap add ios --packagemanager SPM
This creates a new
directory with the SPM-based scaffold. The new structure includes
ios/App/CapApp-SPM/Package.swift
instead of
.
Step 8: Restore Preserved Files
Copy each backed-up file from
back to its original path under
,
overwriting the scaffolded defaults. For example:
bash
cp .spm-migration-backup/ios/App/App/Info.plist ios/App/App/Info.plist
cp .spm-migration-backup/ios/App/App/AppDelegate.swift ios/App/App/AppDelegate.swift
cp -R .spm-migration-backup/ios/App/App/Assets.xcassets ios/App/App/Assets.xcassets
cp -R .spm-migration-backup/ios/App/App/Base.lproj ios/App/App/Base.lproj
Repeat for every file/directory backed up in Step 4,
except ios/App/App.xcodeproj/project.pbxproj
— that file is
not restored. The new
is required for SPM integration; signing values from the old one are reapplied via Step 9.
If any preserved file did not exist in the new scaffold (e.g.,
,
, custom Swift files), the file must additionally be added to the Xcode project membership in Step 10.
Step 9: Reapply Signing & Bundle Configuration
Edit the new
ios/App/App.xcodeproj/project.pbxproj
and reapply the values extracted in Step 5. For each setting, find every occurrence in the
target's
blocks and replace the scaffolded value with the recorded value.
Apply this diff pattern for each setting (example for
PRODUCT_BUNDLE_IDENTIFIER
):
diff
- PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
+ PRODUCT_BUNDLE_IDENTIFIER = <RECORDED_BUNDLE_ID>;
Settings to reapply (use
semantics — update
all occurrences across
and
configurations):
PRODUCT_BUNDLE_IDENTIFIER
PROVISIONING_PROFILE_SPECIFIER
(only if manual signing)
- (only if manual signing)
If
IPHONEOS_DEPLOYMENT_TARGET
in the recorded values is
higher than the scaffolded value, update it. If lower, keep the scaffolded value (the new scaffold targets a supported minimum).
Step 10: Add Restored Custom Files to the Xcode Project
The new
only references files that the scaffold created. Files restored in Step 8 that did
not exist in the scaffold (e.g.,
,
, custom Swift files, Notification Service Extension targets) are present on disk but invisible to Xcode until they are added to the project's
,
, and
entries.
For each custom file that was not part of the scaffold, instruct the user to perform the following one-time action in Xcode (the SPM scaffold has no
— open
directly):
- Open the project: (or
open ios/App/App.xcodeproj
).
- In the Xcode Project Navigator, right-click the group and choose Add Files to "App"….
- Select each restored custom file (e.g., , , custom files).
- Ensure Copy items if needed is unchecked and the target is checked.
For the
file specifically, also set the
build setting in
ios/App/App.xcodeproj/project.pbxproj
. Apply this diff pattern in
both and
blocks for the
target:
diff
CODE_SIGN_STYLE = Automatic;
+ CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
CURRENT_PROJECT_VERSION = 1;
If the project had additional Xcode targets (e.g., a Notification Service Extension under
ios/App/NotificationService/
), the user must recreate those targets in Xcode. SPM scaffolding does not regenerate non-
targets. Inform the user explicitly.
Step 11: Reconfigure Capabilities
For each capability key recorded from
in Step 5, verify the corresponding capability is enabled in Xcode:
- Open via .
- Select the target → Signing & Capabilities tab.
- For each entitlement key, add the matching capability via the + Capability button:
- → Push Notifications
com.apple.security.application-groups
→ App Groups
com.apple.developer.associated-domains
→ Associated Domains
- → Keychain Sharing
com.apple.developer.in-app-payments
→ Apple Pay
com.apple.developer.icloud-services
→ iCloud
This is the only step that requires Xcode interaction. Inform the user explicitly that this step must be done manually.
Step 12: Re-sync Plugins
Run:
bash
npm install
npx cap sync ios
updates
ios/App/CapApp-SPM/Package.swift
to include every Capacitor plugin recorded in Step 2. Watch for warnings — any plugin without SPM support will be reported here.
If a plugin is reported as incompatible with SPM, inform the user. The user must either:
- Wait for upstream SPM support from the plugin maintainer, or
- Fork the plugin and add SPM support using the
capacitor-plugin-spm-support
skill, or
- Remove the plugin from the project.
Step 13: Verify the Build
-
Open the project in Xcode and resolve SPM packages:
In Xcode, File → Packages → Resolve Package Versions (or wait for automatic resolution on open).
-
Build for a simulator from the command line to confirm everything compiles:
-
Verify the app launches and that previously working features (push notifications, deep links, Firebase, etc.) still function.
Step 14: Cleanup
Once the build is verified and the app runs correctly:
-
Delete the backup directory:
bash
rm -rf .spm-migration-backup
-
-
Verify no CocoaPods artifacts remain:
bash
find ios -name 'Podfile*' -o -name 'Pods' -o -name '*.xcworkspace'
If any results appear, delete them. The SPM workflow uses
directly — there is no
.
-
Commit the migration:
bash
git add -A
git commit -m "chore(ios): migrate from CocoaPods to Swift Package Manager"
Error Handling
npx cap add ios --packagemanager SPM
fails with "ios platform already exists" — Step 6 was not completed. Re-run and retry.
- reports a plugin without SPM support — that plugin cannot be used under SPM until it ships a . Inform the user and pause the migration; do not silently drop the plugin.
- Build fails with "Module 'X' not found" — the plugin's SPM package was not resolved. Run File → Packages → Reset Package Caches in Xcode, then File → Packages → Resolve Package Versions.
- Build fails with code signing errors — the values reapplied in Step 9 are missing or incorrect. Re-read
.spm-migration-backup/ios/App/App.xcodeproj/project.pbxproj
and confirm and PRODUCT_BUNDLE_IDENTIFIER
were applied to both and configurations.
- Push notifications stop working after migration — the Push Notifications capability was not re-added in Step 11, or is not linked via (Step 10).
- Firebase initializes but no data appears — was restored to disk but not added to the Xcode project's target membership in Step 10.
- Custom Notification Service Extension target is missing — non- targets are not regenerated by . Recreate the target manually in Xcode and re-add its source files.
- The user reports they want to revert — because Step 1 verified a clean working tree, the entire migration can be undone with followed by
git clean -fd ios .spm-migration-backup
.
Related Skills
capacitor-plugin-spm-support
— For adding SPM support to a Capacitor plugin (not an app). Use this if Step 12 reports a plugin without SPM support and the user controls that plugin's source.
- — For installing or reconfiguring Capacitor plugins after the migration.
- — If the project is on Capacitor 5 or earlier, upgrade it to Capacitor 6+ first using this skill, then return here.
capacitor-app-development
— For general Capacitor app development topics, troubleshooting, and best practices after the migration.