cocoapods-to-spm — Claude Code skill: move Capacitor iOS to SPM
Walks a Capacitor iOS app from CocoaPods to Swift Package Manager and flags the plugins that have no Package.swift.
npx skills add Cap-go/capgo-skills --skill cocoapods-to-spm -a claude-codeWhat cocoapods-to-spm does
The skill starts read-only. It reads package.json, ios/App/Podfile, Podfile.lock and project.pbxproj, then lists every plugin missing a Package.swift and every pod that is not a Capacitor plugin at all — Firebase, analytics SDKs, hand-added pods. Those need a vendor SPM package added to the App target in Xcode after the migration.
Then it picks a route. Most apps go through npx cap spm-migration-assistant, which keeps the existing Xcode project and its customizations. A fresh scaffold is the other option, for projects where ios/ barely differs from the template. The skill tells you not to run a hybrid setup with a Podfile next to CapApp-SPM: the CLI treats a project as SPM only when ios/App/CapApp-SPM/ exists, so renaming or deleting that folder quietly flips npx cap sync back to CocoaPods.
A few things bite later. ios/App/CapApp-SPM/Package.swift is regenerated on every npx cap sync ios, so put customizations in capacitor.config.* instead. A plugin ships only if its npm package root has a Package.swift; the rest are skipped with a warning and their native code is missing at runtime. Plain .framework files are not valid SPM binary targets — you need an .xcframework. Capacitor 8.x and 9.x are supported; on 6.x or 7.x the skill suggests upgrading first.
What it helps with
- move an existing Capacitor iOS app off CocoaPods onto SPM
- find plugins that ship no
Package.swiftand decide what to do with them - recover a
npx cap spm-migration-assistantrun that stopped halfway - deal with the "plugins are not compatible with SPM" warning from
npx cap sync - get an app ready for Capacitor 9, where SPM is the default
Example prompts for the agent
- migrate my Capacitor app from CocoaPods to SPM
- why does
npx cap syncsay a plugin is not compatible with SPM? - prepare the ios project for Capacitor 9 and drop the Podfile
How to install cocoapods-to-spm
1Skills CLI (Vercel)
npx skills add Cap-go/capgo-skills --skill cocoapods-to-spm -a claude-code2Manually: put the files in place
~/.claude/skills/cocoapods-to-spm/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/cocoapods-to-spm/
1Skills CLI (Vercel)
npx skills add Cap-go/capgo-skills --skill cocoapods-to-spm -a codex2Manually: put the files in place
.agents/skills/cocoapods-to-spm/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add Cap-go/capgo-skills --skill cocoapods-to-spm -a cursor2Manually: put the files in place
.cursor/skills/cocoapods-to-spm/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install Cap-go/capgo-skills cocoapods-to-spm2Manually: put the files in place
.github/skills/cocoapods-to-spm/SKILL.md1Skills CLI (Vercel)
npx skills add Cap-go/capgo-skills --skill cocoapods-to-spm -a gemini-cli2Platform CLI
gemini skills install https://github.com/Cap-go/capgo-skills --path skills/cocoapods-to-spmManually: put the files in place
.devin/skills/cocoapods-to-spm/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add Cap-go/capgo-skills --skill cocoapods-to-spm -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about cocoapods-to-spm
How do I install cocoapods-to-spm in Claude?
Run npx skills add Cap-go/capgo-skills --skill cocoapods-to-spm -a claude-code. The skill then lives at ~/.claude/skills/cocoapods-to-spm/SKILL.md.
What does cocoapods-to-spm do?
It migrates the iOS side of a Capacitor app from CocoaPods to Swift Package Manager: inspects pods and plugins, finds blockers, and chooses between npx cap spm-migration-assistant and a fresh scaffold.
Which Capacitor versions does it support?
Capacitor 8.x and 9.x. On 6.x or 7.x the skill suggests upgrading Capacitor before switching to SPM.
Source text
SKILL.md · MIT
---
name: cocoapods-to-spm
description: Migrates an existing Capacitor iOS app from CocoaPods to Swift Package Manager (SPM) before CocoaPods Trunk goes read-only (expected December 2, 2026). Use when the app still has ios/App/Podfile, Pods/, Podfile.lock or App.xcworkspace and the user wants CapApp-SPM, runs or recovers from `npx cap spm-migration-assistant`, needs debug.xcconfig / CAPACITOR_DEBUG wired up, sees "Some installed Capacitor plugins are not compatible with SPM" or "<plugin> does not have a Package.swift", or plans the Capacitor 9 upgrade where SPM is the default. Do not use for adding SPM support inside a plugin repository (use capacitor-plugin-spm-support), the UIScene lifecycle migration (use capacitor-uiscene-migration), full version upgrades (use capacitor-app-upgrades), or generic iOS build debugging (use debugging-capacitor).
---
# CocoaPods to Swift Package Manager Migration
Move a Capacitor iOS app from CocoaPods to SPM without losing native project customizations.
## When to Use
TRIGGER when:
- `ios/App/Podfile`, `Pods/`, `Podfile.lock`, or `App.xcworkspace` exists and the user wants SPM.
- User asks about `npx cap spm-migration-assistant`, `CapApp-SPM`, `debug.xcconfig`, or `--packagemanager SPM`.
- User worries about CocoaPods Trunk becoming read-only (expected December 2, 2026), or about pods that can no longer be published/updated.
- `npx cap sync` warns `Some installed Capacitor plugins are not compatible with SPM` or `<plugin id> does not have a Package.swift`.
- Upgrading to Capacitor 9 and wanting to drop CocoaPods at the same time.
Do not use:
- Adding `Package.swift` / `CAPBridgedPlugin` to a plugin you maintain -> `capacitor-plugin-spm-support`.
- SceneDelegate / UIScene adoption (Capacitor 8.5+, required by Xcode 27) -> `capacitor-uiscene-migration`.
- The Capacitor major upgrade itself -> `capacitor-app-upgrades`, `capacitor-app-upgrade-v8-to-v9`.
- Build or runtime failures unrelated to dependency management -> `debugging-capacitor`.
## Why Now
- CocoaPods Trunk is expected to become read-only on **December 2, 2026**. Existing pods keep resolving, but no new pod versions get published, so plugin fixes will increasingly ship SPM-only.
- Capacitor 8+ scaffolds new iOS projects with SPM by default. Upgrading Capacitor does **not** convert an existing CocoaPods app.
- Capacitor 9 (currently `next` / alpha) only adds the `Cordova` product / `CapacitorCordova` pod when a Cordova plugin is installed. Native code that imports `Cordova` without a Cordova plugin breaks in either setup.
## How the CLI Decides
- The CLI treats the project as SPM if and only if `ios/App/CapApp-SPM/` exists. Deleting or renaming it silently flips `npx cap sync` back to CocoaPods behavior.
- `ios/App/CapApp-SPM/Package.swift` is regenerated on every `npx cap sync ios` (`// DO NOT MODIFY THIS FILE - managed by Capacitor CLI commands`). Put customizations in `capacitor.config.*`, never in that file.
- A plugin is included only if its npm package root has a `Package.swift`. Others are skipped with a warning; their native code is silently missing at runtime (`"X" plugin is not implemented on ios`).
- During sync, if a plugin's `Package.swift` pins `capacitor-swift-pm` to a different major, the CLI rewrites that file inside `node_modules` and warns `<id> is built for Capacitor <N>, it might cause issues`. Treat this as "upgrade the plugin", not as success.
- Cordova plugins get a generated package under `ios/capacitor-cordova-ios-plugins/sources/<Name>`. Plain `.framework` files are not supported as SPM binary targets (warning: `custom .framework files are not supported as binaryTarget in SPM`); they need an `.xcframework`.
## Command Policy
- Use the repo's package manager for installs and scripts; keep Capacitor CLI calls as `npx cap ...` so the project-local CLI runs.
- Run commands from the directory containing `capacitor.config.*`.
## Live Project Snapshot
Detected Capacitor, iOS, Cordova, and plugin dependencies:
!`node -e "const fs=require('fs');if(!fs.existsSync('package.json'))process.exit(0);const pkg=JSON.parse(fs.readFileSync('package.json','utf8'));const out=[];for(const section of ['dependencies','devDependencies']){for(const [name,version] of Object.entries(pkg[section]||{})){if(name.startsWith('@capacitor/')||name.startsWith('@capgo/')||name.startsWith('@capacitor-community/')||name.startsWith('@awesome-cordova-plugins/')||name.startsWith('cordova-')||name.includes('capacitor'))out.push(section+'.'+name+'='+version)}}console.log(out.sort().join('\n'))"`
Relevant iOS dependency files:
!`find ios -maxdepth 5 \( -name 'Podfile' -o -name 'Podfile.lock' -o -name 'Pods' -o -name 'App.xcworkspace' -o -name 'Package.swift' -o -name 'Package.resolved' -o -name 'CapApp-SPM' -o -name 'debug.xcconfig' -o -name 'project.pbxproj' -o -name 'Info.plist' -o -name '*.entitlements' -o -name 'GoogleService-Info.plist' \) 2>/dev/null`
## Procedure
### 1. Inspect (read-only)
1. Capacitor version in `package.json` (`@capacitor/ios`). 8.x and 9.x are supported; on 6.x/7.x, prefer upgrading first.
2. `ios/App/Podfile` and `Podfile.lock`: list every pod, and which ones are not Capacitor plugins (Firebase, analytics SDKs, custom pods added by hand).
3. For each Capacitor/Cordova plugin: does `node_modules/<pkg>/Package.swift` exist? Cordova plugins get a generated package, so only `NO` entries block:
```bash
node -e "const fs=require('fs');const p=require('./package.json');for(const n of Object.keys({...p.dependencies,...p.devDependencies})){const d='node_modules/'+n;if(!fs.existsSync(d+'/ios')&&!fs.existsSync(d+'/plugin.xml'))continue;console.log((fs.existsSync(d+'/Package.swift')?'SPM ':fs.existsSync(d+'/plugin.xml')?'CORDOVA ':'NO ')+n)}"
```
4. `ios/App/App.xcodeproj/project.pbxproj`: signing, custom build settings, extra targets (widgets, notification service extensions), build phases, schemes.
5. `ios/App/App/`: `AppDelegate.swift`, `SceneDelegate.swift`, `Info.plist`, entitlements, `GoogleService-Info.plist`, custom Swift/ObjC files.
Report findings before editing: plugins without SPM, non-plugin pods that need an SPM replacement, and native customizations at risk.
### 2. Resolve blockers
For every `NO` plugin or non-plugin pod:
- Upgrade the plugin (most maintained plugins, including `@capacitor/*` and `@capgo/*`, ship `Package.swift`).
- Replace it with a maintained SPM-capable alternative.
- If the project owns the plugin, convert it with `capacitor-plugin-spm-support`.
- For third-party SDK pods added directly in the Podfile, add the vendor's SPM package to the App target in Xcode after migration.
Ask the user before proceeding if a critical plugin has no SPM path. Do not keep a hybrid Podfile + CapApp-SPM setup.
### 3. Choose a path
| Path | Use when |
|------|----------|
| `npx cap spm-migration-assistant` | Most apps; keeps the existing Xcode project and its customizations. |
| Fresh scaffold | `ios/` is close to the template (no extensions, few native edits). |
| Manual repair | The assistant ran partially or the project is heavily customized. |
### 4a. Run the migration assistant
Commit first. Requires CocoaPods (or Bundler) still installed because it runs `pod deintegrate`.
```bash
npx cap spm-migration-assistant
```
It runs `pod deintegrate`, deletes `Podfile`, `Podfile.lock`, and `App.xcworkspace`, extracts `ios/App/CapApp-SPM/`, writes `ios/debug.xcconfig` (`CAPACITOR_DEBUG = true`), adds `CAPACITOR_DEBUG = $(CAPACITOR_DEBUG)` to `Info.plist` if missing, then runs an iOS update that generates `Package.swift`. It ends with `To complete migration follow the manual steps at https://capacitorjs.com/docs/ios/spm#using-our-migration-tool`.
Two manual Xcode steps remain (`npx cap open ios`, which now opens `App.xcodeproj`):
1. Project `App` -> Package Dependencies -> `+` -> Add Local... -> select `ios/App/CapApp-SPM` -> Add Package, and link the `CapApp-SPM` library to the `App` target.
2. Project `App` -> Info -> Configurations -> Debug -> set the configuration file to `debug.xcconfig`.
Then `npx cap sync ios`.
### 4b. Fresh scaffold
Back up everything listed in step 1.5 first.
```bash
rm -rf ios
npx cap add ios --packagemanager SPM
npx cap sync ios
```
On Capacitor 8.5+ the new template already includes `SceneDelegate.swift` and `UIApplicationSceneManifest`. Restore icons, `Info.plist` keys, entitlements, signing, Firebase plist, custom sources, and extension targets by merging, not overwriting template files.
### 5. Optional SPM config in `capacitor.config.*`
Only when needed (all under `experimental.ios.spm`, verify the installed CLI version supports them):
- `swiftToolsVersion` (8.3+, default `'5.9'`): header of the generated `Package.swift`.
- `packageTraits` (8.3+): `{ "<plugin id>": ["TraitName", ".defaults"] }`; requires `swiftToolsVersion` >= `'6.1'`.
- `packageOptions` (8.4+): `{ "<plugin id>": { symlink: true, moduleAliases: { "Target": "Alias" } } }` for package name or module collisions.
### 6. UIScene check
The assistant changes dependency management only. If `Info.plist` lacks `UIApplicationSceneManifest`, the app is not on the scene lifecycle that Capacitor 8.5+ and Xcode 27 expect; run `npx cap migrate` or follow `capacitor-uiscene-migration`.
## Verification
```bash
# No CocoaPods leftovers
ls ios/App | grep -E 'Podfile|Pods|xcworkspace' && echo "LEFTOVERS" || echo "clean"
# CLI sees SPM and every plugin is included
npx cap sync ios 2>&1 | grep -Ei 'Package.swift|not compatible|built for Capacitor'
grep -c '.package(name:' ios/App/CapApp-SPM/Package.swift
# Plugin classes registered for runtime
node -e "console.log(require('./ios/App/App/capacitor.config.json').packageClassList)"
# Build without Xcode UI (project, not workspace)
xcodebuild -project ios/App/App.xcodeproj -scheme App -destination 'generic/platform=iOS Simulator' build
```
Also confirm in Xcode: `CapApp-SPM` linked to the App target, Debug uses `debug.xcconfig`, signing/bundle id/capabilities unchanged. On device, exercise each native plugin once (camera, push, purchases, etc.).
Commit `ios/App/CapApp-SPM/` and `ios/debug.xcconfig`. Follow `CapApp-SPM/.gitignore` for `Package.resolved` (newer templates ignore it).
## Error Handling
Load `references/troubleshooting.md` for the full table. Most common:
| Message / symptom | Fix |
|---|---|
| `Capacitor project is already using SPM, exiting.` | `CapApp-SPM/` already exists. Do manual repair, not the assistant. |
| `CocoaPods is not installed.` (assistant) | Install CocoaPods temporarily or use the fresh scaffold path. |
| `<id> does not have a Package.swift` / `Some installed Capacitor plugins are not compatible with SPM` | Upgrade, replace, or convert that plugin (step 2). |
| `"X" plugin is not implemented on ios` at runtime | Plugin skipped from `Package.swift`, or `packageClassList` missing it. Fix the plugin, re-sync, clean build. |
| `No such module 'Capacitor'` / `Missing package product 'CapApp-SPM'` | Local package not added/linked; redo manual step 1, then File -> Packages -> Reset Package Caches. |
| Debug-only features off (no WebView inspection, no debug logs) | `debug.xcconfig` not set on Debug configuration. |
## Output Format
For planning, return: current state (Capacitor version, pods, plugins without SPM, customizations at risk), chosen path and why, ordered steps, remaining Xcode/device-only checks.
© Cap-go · MIT · Cap-go/capgo-skills