capacitor-plugin-spm-support · cap-go — Claude Code skill: setup & details
Adds Swift Package Manager support to Capacitor plugin repositories while keeping CocoaPods compatibility. Fixes missing Package.swift errors in SPM apps.
npx skills add Cap-go/capgo-skills --skill capacitor-plugin-spm-support -a claude-codeWhat capacitor-plugin-spm-support does
The skill restructures the iOS layer of a Capacitor plugin repo. It shifts native code from ios/Plugin/ into ios/Sources/<ClassName>/ and moves tests to ios/Tests/. It deletes the old .m and .h bridge files, replacing the CAP_PLUGIN macro with the CAPBridgedPlugin protocol in Swift.
The root Package.swift file must sit next to package.json at the package root. The Capacitor CLI ignores manifests inside ios/. Both the package name and library product must strictly match the PascalCase translation of the npm package name, such as CapgoCapacitorUpdater.
Existing CocoaPods setups keep working because the skill updates .podspec paths to point to ios/Sources. For Capacitor 9, it removes the unconditional Cordova product dependency. Third-party CocoaPods dependencies are verified for official SPM support before editing files.
What it helps with
- Add Swift Package Manager support to a Capacitor plugin
- Resolve “does not have a Package.swift” warnings during
npx cap sync ios - Migrate native iOS bridge code to the
CAPBridgedPluginprotocol - Update source paths in
.podspecandpackage.json
Example prompts for the agent
- Add Swift Package Manager support to this Capacitor plugin
- Fix the missing Package.swift warning for our iOS plugin
- Convert this plugin's iOS bridge to CAPBridgedPlugin and generate Package.swift
How to install capacitor-plugin-spm-support
1Skills CLI (Vercel)
npx skills add Cap-go/capgo-skills --skill capacitor-plugin-spm-support -a claude-code2Manually: put the files in place
~/.claude/skills/capacitor-plugin-spm-support/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/capacitor-plugin-spm-support/
1Skills CLI (Vercel)
npx skills add Cap-go/capgo-skills --skill capacitor-plugin-spm-support -a codex2Manually: put the files in place
.agents/skills/capacitor-plugin-spm-support/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add Cap-go/capgo-skills --skill capacitor-plugin-spm-support -a cursor2Manually: put the files in place
.cursor/skills/capacitor-plugin-spm-support/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install Cap-go/capgo-skills capacitor-plugin-spm-support2Manually: put the files in place
.github/skills/capacitor-plugin-spm-support/SKILL.md1Skills CLI (Vercel)
npx skills add Cap-go/capgo-skills --skill capacitor-plugin-spm-support -a gemini-cli2Platform CLI
gemini skills install https://github.com/Cap-go/capgo-skills --path skills/capacitor-plugin-spm-supportManually: put the files in place
.devin/skills/capacitor-plugin-spm-support/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add Cap-go/capgo-skills --skill capacitor-plugin-spm-support -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 capacitor-plugin-spm-support
How to install capacitor-plugin-spm-support in Claude Code?
Run npx skills add Cap-go/capgo-skills --skill capacitor-plugin-spm-support -a claude-code in your terminal.
What does capacitor-plugin-spm-support do?
It configures an existing Capacitor plugin for Swift Package Manager. It creates a root Package.swift, shifts files into ios/Sources/, and converts Objective-C macro bridges to CAPBridgedPlugin.
Does adding SPM break CocoaPods support?
No, the skill retains the .podspec file. It updates s.source_files to ios/Sources/**/*.{...}, allowing CocoaPods apps to consume the plugin as before.
Source text
SKILL.md · MIT
---
name: capacitor-plugin-spm-support
description: Adds Swift Package Manager support to an existing Capacitor plugin repository so apps using CapApp-SPM can consume it after CocoaPods Trunk goes read-only (expected December 2, 2026). Covers root Package.swift naming rules the Capacitor CLI depends on, CAPBridgedPlugin conversion (identifier, jsName, pluginMethods), removing the ObjC CAP_PLUGIN bridge (.m/.h), Sources/Tests layout, podspec and package.json files updates, third-party pod to SPM dependency mapping, resources/PrivacyInfo.xcprivacy, binary xcframeworks, and Capacitor 9 removal of the unconditional Cordova product. Use when app sync warns "<plugin> does not have a Package.swift" for a plugin you own, or "plugin is not implemented on ios" only in SPM apps. Do not use for migrating an app project (use cocoapods-to-spm), version bumps of plugins (use capacitor-plugin-upgrades / capacitor-plugin-upgrade-v8-to-v9), or choosing plugins (use capacitor-plugins).
---
# Add Swift Package Manager Support to a Capacitor Plugin
Make a plugin installable in SPM-based Capacitor apps while keeping its podspec for CocoaPods apps.
## When to Use
TRIGGER when:
- A plugin repo has `ios/Plugin/*.swift` + `*.m`/`*.h` with `CAP_PLUGIN(...)` and no root `Package.swift`.
- An app's `npx cap sync ios` prints `<plugin id> does not have a Package.swift` for a plugin the user maintains.
- A plugin works with CocoaPods but JS gets `"X" plugin is not implemented on ios` in SPM apps.
- Preparing a plugin for Capacitor 9 / CocoaPods Trunk read-only.
Do not use:
- App-side CocoaPods removal -> `cocoapods-to-spm`.
- Capacitor major bumps of a plugin -> `capacitor-plugin-upgrades`, `capacitor-plugin-upgrade-v8-to-v9`.
- Android-only plugin work, or non-Capacitor (pure Cordova, React Native) plugins.
## Rules the CLI Enforces (non-obvious)
- `Package.swift` must be at the **npm package root** (next to `package.json`), not under `ios/`. The app CLI only checks `<plugin root>/Package.swift`.
- The CLI references the plugin in the app's `CapApp-SPM/Package.swift` as `.package(name: "<Name>", path: ...)` and `.product(name: "<Name>", package: "<Name>")`, where `<Name>` is derived from the npm name: drop `@`, turn `/` and `-` into word breaks, PascalCase. `@capgo/capacitor-updater` -> `CapgoCapacitorUpdater`, `@acme/capacitor-foo-bar` -> `AcmeCapacitorFooBar`. The `Package(name:)` **and** the `.library(name:)` product must both equal `<Name>`, or app builds fail to resolve the product.
- Runtime registration uses `ios/App/App/capacitor.config.json` -> `packageClassList`, filled by scanning the plugin's iOS sources (`capacitor.ios.src` in package.json, default `ios`) for `@objc(ClassName)`. Keep `@objc(<Class>Plugin)` on the class; without it the plugin is never registered.
- The `capacitor-swift-pm` dependency must use `from: "<major>.0.0"`. If the major differs from the app's Capacitor, app sync rewrites it in `node_modules` and warns `<id> is built for Capacitor <N>, it might cause issues`.
- Capacitor 9: depend only on `.product(name: "Capacitor", package: "capacitor-swift-pm")`. Remove the unconditional `Cordova` product; it is only present in apps that install a Cordova plugin. Keep it on Capacitor 8 only if your code imports Cordova.
## Procedure
### 1. Inspect
Read `package.json` (name, `files`, `capacitor.ios.src`, scripts), the `.podspec` (`s.name`, `s.ios.deployment_target`, `s.dependency`, `s.resources`, `s.vendored_frameworks`), every file in `ios/`, and the `CAP_PLUGIN(...)` macro in the `.m` file.
Record: SPM `<Name>` (rule above), class name, `jsName`, every `CAP_PLUGIN_METHOD(name, returnType)`, iOS deployment target, third-party pods, resources, vendored frameworks, ObjC/C sources other than the bridge.
Check each third-party pod has an official SPM package. Report any that do not before editing; ask whether to drop, vendor as `.xcframework`, or keep the plugin CocoaPods-only.
### 2. Fast path: converter
If the iOS code is Swift-only apart from the `[Name]Plugin.m` / `.h` bridge, the official converter does most of the work (adds `CAPBridgedPlugin`, creates `Package.swift`, moves code to `ios/Sources` and `ios/Tests`, removes `Plugin.xcodeproj`/`xcworkspace`/`Podfile`, updates podspec `source_files`, `files`, and `verify:ios`): https://github.com/ionic-team/capacitor-plugin-converter. Review its diff against the rules above, especially the package/product name.
### 3. Manual conversion
1. Move sources: `ios/Plugin/` -> `ios/Sources/<ClassName>/`, tests -> `ios/Tests/<ClassName>Tests/`.
2. Convert the class (keep method names and return types exactly as in the `.m` macro):
```swift
import Capacitor
@objc(FooPlugin)
public class FooPlugin: CAPPlugin, CAPBridgedPlugin {
public let identifier = "FooPlugin" // first CAP_PLUGIN argument
public let jsName = "Foo" // second CAP_PLUGIN argument (registerPlugin name)
public let pluginMethods: [CAPPluginMethod] = [
CAPPluginMethod(name: "doThing", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "watch", returnType: CAPPluginReturnCallback),
CAPPluginMethod(name: "fireAndForget", returnType: CAPPluginReturnNone)
]
@objc func doThing(_ call: CAPPluginCall) { call.resolve() }
}
```
3. Delete `FooPlugin.m` and `FooPlugin.h` (the `CAP_PLUGIN` bridge) and stale `Plugin.xcodeproj`, `Plugin.xcworkspace`, `Podfile`, `Info.plist` files under `ios/`.
4. Add root `Package.swift`. Template and variants (resources, binary targets, third-party packages, mixed ObjC) in `references/package-swift-templates.md`.
5. Update the podspec: `s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'`, keep `s.dependency 'Capacitor'`, and mirror deployment target and resources.
6. Update `package.json`:
- `files`: add `ios/Sources/`, `Package.swift`; keep the `.podspec`; remove old `ios/Plugin/`.
- `verify:ios`: `xcodebuild -scheme <Name> -destination generic/platform=iOS`.
## Verification
```bash
# Package resolves and builds standalone (from plugin root)
swift package describe >/dev/null && echo "manifest ok"
xcodebuild -scheme <Name> -destination generic/platform=iOS build
# Published tarball contains the manifest and sources
npm pack --dry-run 2>&1 | grep -E 'Package.swift|ios/Sources'
# No leftover ObjC bridge
grep -rn "CAP_PLUGIN(" ios/ && echo "LEFTOVER BRIDGE"
```
Then in an SPM example/test app (directory with `capacitor.config.*`): install the plugin (`npm install ../path` or a packed tarball), run `npx cap sync ios`, confirm no `does not have a Package.swift` warning, confirm `<Name>` appears in `ios/App/CapApp-SPM/Package.swift` and the class appears in `ios/App/App/capacitor.config.json` `packageClassList`, build, and call one method from JS. Also run `pod lib lint` (or a CocoaPods example app) if the podspec is still published.
## Error Handling
| Error | Fix |
|---|---|
| `product '<Name>' required by package 'capapp-spm' target 'CapApp-SPM' not found in package '<Name>'` | `Package(name:)` / `.library(name:)` do not match the CLI-derived `<Name>`. |
| `"Foo" plugin is not implemented on ios` | Missing `@objc(FooPlugin)`, wrong `jsName`, or class not in `packageClassList`; re-sync after fixing. |
| `"Foo.bar()" is not implemented on ios` | Method missing from `pluginMethods` or not marked `@objc`. |
| `No such module 'Capacitor'` building the package | Missing `capacitor-swift-pm` dependency or wrong product name. |
| `public headers ("include") directory path for 'X' is invalid or not contained in the target` | Mixed ObjC target without `publicHeadersPath`; split ObjC into its own target (see reference). |
| `<id> is built for Capacitor <N>, it might cause issues` (in app) | Bump `capacitor-swift-pm` `from:` to the app's major. |
| `Cordova` product missing (Capacitor 9 app) | Remove the `Cordova` product dependency. |
| Resource not found at runtime | Declare `resources:` and load via `Bundle.module`, not `Bundle.main`. |
## References
Only load when the topic is in play:
- `references/package-swift-templates.md`: base manifest, resources and privacy manifest, third-party SPM dependencies, binary `.xcframework`, mixed Swift/ObjC targets, Capacitor 8 vs 9 dependency blocks.
© Cap-go · MIT · Cap-go/capgo-skills
Similar skills
Everything for Mobile →capacitor-plugin-spm-support by other authors comes first.