← All articles

Five Steps to Migrate a Capacitor Plugin to SPM Support

Rename plugin entry files, run capacitor-plugin-converter, scaffold a fresh plugin template, replace the ios folder and podspec paths, then mirror CocoaPods dependencies in Package.swift.

Published
Five Steps to Migrate a Capacitor Plugin to SPM Support cover image

Let’s migrate a Capacitor iOS plugin so it supports SPM as well as CocoaPods. For why SPM matters and what you gain, see:

https://zenn.dev/rdlabo/articles/4456315c9ca829https://zenn.dev/rdlabo/articles/4456315c9ca829

1. Rename the Plugin Files

Here, “plugin files” means the header and .m files under ios/Plugin, plus any Swift file marked with @objc that Capacitor loads first as the plugin entry. Older layouts look like this:

  • Plugin.swift
  • Plugin.m
  • Plugin.h

That is confusing, so rename to the newer layout below. ** is the plugin name — for the AdMob plugin, read **Plugin.swift as AdMobPlugin.swift.

  • **Plugin.swift
  • **Plugin.m
  • **Plugin.h

You will delete Plugin.xcodeproj and similar files eventually, so you do not need to rename in Xcode to fix references — changing filenames on disk is enough. Next, open **Plugin.swift. If the type is not named **Plugin, rename it:

- @objc(Stripe)
- public class Plugin: CAPPlugin {
+ @objc(StripePlugin)
+ public class StripePlugin: CAPPlugin {

2. Convert with capacitor-plugin-converter

Use https://github.com/ionic-team/capacitor-plugin-converter to automate what it can. The workflow is simple. First download the zip anywhere:

% curl -OL https://github.com/ionic-team/capacitor-plugin-converter/releases/latest/download/cap2spm.zip

Double-click to get a Unix executable, then run it. Suppose the payment plugin you are migrating lives in ./payment relative to where you downloaded the tool, and the ** part of **Plugin.swift is PaymentPlugin. Run:

% ./cap2spm payment --no-backup

Without --no-backup, existing files are renamed to .old. If you manage the plugin in Git, you usually do not need that. The tool deletes **Plugin.h and **Plugin.m and moves their contents into the pluginMethods property on **Plugin.swift, similar to:

https://github.com/capacitor-community/admob/blob/master/ios/Sources/AdMobPlugin/AdMobPlugin.swift#L12-L29https://github.com/capacitor-community/admob/blob/master/ios/Sources/AdMobPlugin/AdMobPlugin.swift#L12-L29

SPM does not load header and .m files, so this migration is required. A Package.swift is also generated at the top level.

3. Create a Fresh Plugin Inside the Repo

I tried several approaches; copying the ios folder into a brand-new plugin scaffold caused the fewest problems. Create one like this:

% cd payment # Assume payment is the plugin directory for this example
% npm init @capacitor/plugin@latest -- --package-id com.hoge.huga --repo https://example.com --author "hoge" --license MIT --description "hoge" ## Create a new plugin to copy into the plugin directory

To avoid extra prompts, I fill optional fields with placeholders in the command. You will still be asked:

✔ What should be the npm package of your plugin?
 … Enter the current plugin's npm name
✔ What directory should be used for your plugin?
… Plugin directory name. Use `new-template` here.
✔ What should be the class name for your plugin?
… Enter the value for the "**" part of **Plugin.swift**.

npm install runs after files are generated; you can cancel midway if you like.

4. Refresh the iOS Folder

Next, rebuild the layout: take what you need from new-template and keep what you still need from the old tree. Do not change Web or Android code here — be careful not to delete or overwrite them by mistake.

4.1. Overwrite new-template with Existing Plugin Code

Move existing plugin code (contents of ios/plugins) into new-template/ios/Sources/**Plugin.

4.2. Overwrite the Existing ios Folder with new-template/ios

You can delete the old ios folder. The structure changed a lot from legacy plugins; you could delete obsolete files and fix paths by hand, but for maintainability a clean layout is better.

Delete the old ios folder and put new-template/ios in its place.

4.4. Overwrite the Top-Level Package.swift with new-template/Package.swift

Step 2 also generated a Package.swift, but overwrite it with the one from the new scaffold. In most cases the freshly generated manifest is cleaner than the converter output.

4.5. Overwrite the Top-Level package.json with new-template/package.json

Overwrite package.json as well. The files field for npm publish changed, and older plugins often pin very different devDependency versions. At minimum, review the diff.

4.6 Delete new-template

Delete the new-template directory — you no longer need it.

4.7. Update the **.podspec File

Update the iOS directory path in the podspec:

- s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
+ s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'

5. Update Package.swift

If you have dependencies, update Package.swift. Suppose the podspec had:

  s.dependency 'StripePaymentSheet', '~> 23.32.0'
  s.dependency 'StripeApplePay', '~> 23.32.0'

You need the same in Package.swift:

    dependencies: [
        .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", branch: "main"),
+       .package(url: "https://github.com/stripe/stripe-ios-spm.git", branch: "main")
    ],
    targets: [
        .target(
            name: "StripePlugin",
            dependencies: [
                .product(name: "Capacitor", package: "capacitor-swift-pm"),
                .product(name: "Cordova", package: "capacitor-swift-pm"),
+               .product(name: "StripePaymentSheet", package: "stripe-ios-spm"),
+               .product(name: "StripeApplePay", package: "stripe-ios-spm")
            ],
            path: "ios/Sources/StripePlugin"),

Because you maintain two package managers, remember to update both podspec and Package.swift when dependencies change.
That finishes the migration!

Summary

I wish capacitor-plugin-converter automated a bit more, but Capacitor plugin layouts evolved over a long time, so some manual steps are inevitable. Copying folders back and forth looks messy, but if you know the plan upfront it is straightforward work — give it a try.

See you next time.