How to Use SPM Package Traits in Capacitor 8¶
Capacitor 8 now supports Swift Package Manager (SPM) package traits, bringing feature-flag-like capabilities to your iOS plugin dependencies. If you've ever needed to toggle an optional dependency — like enabling SQLCipher encryption for a SQLite plugin — you can now do it directly from your Capacitor config. This was previously only possible with CocoaPods subspecs, and since SPM is the default package manager in Capacitor, this fills an important gap.
What Are SPM Package Traits?¶
Package traits were introduced in SE-0450, a Swift Evolution proposal that shipped with Swift 6.1. In short, traits act as feature flags for Swift packages. They let package authors:
- Conditionally compile code using
#if TraitNamedirectives - Toggle optional dependencies so consumers only pull in what they need
- Define default traits that activate automatically unless explicitly disabled
If you're familiar with CocoaPods subspecs or Cargo features in Rust, traits serve a similar purpose. A plugin can declare a set of traits, and the consumer picks which ones to enable.
For example, a SQLite package might define a default SQLite trait that uses the system-provided SQLite, and an optional SQLCipher trait that swaps in an encrypted variant. The consumer decides at build time which variant they want — without the plugin author needing to maintain separate packages.
Why This Matters for Capacitor¶
When Capacitor moved to SPM as the default package manager (starting with Capacitor 8), plugin authors lost the ability to offer optional dependency variants. CocoaPods had subspecs for this, but SPM had no equivalent — until now.
Without trait support, a plugin author who wanted to offer an optional feature like SQLCipher encryption had two choices: bundle the dependency unconditionally (increasing binary size and adding compliance concerns), or maintain a separate plugin package entirely. Neither option was great.
With Capacitor CLI 8.3.0+, the generated Package.swift can now include trait information, letting you opt in to exactly the features you need.
How It Works¶
Capacitor introduces two new configuration options under the experimental namespace in your capacitor.config.json:
experimental.ios.spm.swiftToolsVersion— Sets theswift-tools-versionin the generatedPackage.swift. Must be"6.1"or higher for traits to work.experimental.ios.spm.packageTraits— Maps plugin package names to arrays of trait names you want to enable.
Here's what the configuration looks like in practice:
{
"appId": "com.example.app",
"appName": "MyApp",
"experimental": {
"ios": {
"spm": {
"swiftToolsVersion": "6.1",
"packageTraits": {
"@capawesome-team/capacitor-sqlite": ["SQLCipher"]
}
}
}
}
}
After running npx cap sync, the Capacitor CLI generates a CapApp-SPM/Package.swift that includes traits: ["SQLCipher"] in the dependency line for the SQLite plugin and uses swift-tools-version: 6.1 at the top of the file.
Real-World Example: The SQLite Plugin¶
The SQLite plugin is one of the first Capacitor plugins to use SPM package traits. It offers optional SQLCipher encryption support that you can enable via a trait.
The plugin's Package.swift defines two traits:
traits: [
.default(enabledTraits: ["SQLite"]),
.trait(
name: "SQLite",
description: "Uses the system-provided SQLite."
),
.trait(
name: "SQLCipher",
description: "Enables SQLCipher encryption support."
)
]
When the SQLCipher trait is enabled, the plugin conditionally includes the SQLCipher dependency and sets a compile-time flag:
targets: [
.target(
name: "SqlitePlugin",
dependencies: [
// ...
.product(name: "SQLCipher", package: "SQLCipher.swift",
condition: .when(traits: ["SQLCipher"]))
],
swiftSettings: [
.define("CAPAWESOME_INCLUDE_SQLCIPHER",
.when(traits: ["SQLCipher"]))
])
]
In the Swift source code, the plugin uses #if directives to guard encryption-specific functionality:
#if CAPAWESOME_INCLUDE_SQLCIPHER
try connection.key(encryptionKey)
#else
throw CustomError.unavailable
#endif
This way, apps that don't need encryption get a smaller binary without the SQLCipher dependency, while apps that do need it simply enable the trait in their Capacitor config.
For Plugin Authors¶
If you maintain a Capacitor plugin and want to add trait support, here's what you need to do in your Package.swift:
1. Set the Swift tools version to 6.1:
2. Define your traits:
traits: [
.default(enabledTraits: ["Default"]),
.trait(name: "Default", description: "Standard behavior."),
.trait(name: "OptionalFeature", description: "Enables an optional feature.")
]
3. Add conditional dependencies:
dependencies: [
.package(
url: "https://github.com/example/optional-lib.git",
from: "1.0.0"
)
],
targets: [
.target(
name: "MyPlugin",
dependencies: [
.product(name: "OptionalLib", package: "optional-lib",
condition: .when(traits: ["OptionalFeature"]))
],
swiftSettings: [
.define("INCLUDE_OPTIONAL_FEATURE",
.when(traits: ["OptionalFeature"]))
])
]
4. Guard your code with conditional compilation:
Keep in mind that traits should be additive — enabling a trait should add functionality, not remove it. This aligns with semantic versioning expectations and avoids surprises for consumers.
Requirements and Limitations¶
This feature is currently marked as experimental because Capacitor does not yet officially support Swift 6. Here's what you need:
| Requirement | Minimum Version |
|---|---|
| Capacitor CLI | 8.3.0+ |
| Xcode | 16.3+ |
| Swift | 6.1+ |
A few things to keep in mind:
- Compatibility: Setting
swift-tools-versionto6.1can cause issues with dependencies that were built with older Swift versions, particularly those using XCFrameworks. Test your app thoroughly after enabling this setting. - Experimental namespace: Both config options live under the
experimentalblock, signaling that the API may change in future Capacitor versions. - Swift 6 timeline: The Capacitor team has indicated that full Swift 6 support is planned for a future major version (likely Capacitor 9), at which point these settings may move out of the experimental namespace.
Try Capawesome Cloud¶
Build, deploy, and update your Capacitor apps with ease using Capawesome Cloud — with native builds, live updates, and app store publishing all in one platform.
Conclusion¶
SPM package traits bring much-needed parity with CocoaPods for managing optional dependencies in Capacitor plugins. For app developers, it means you can now opt in to features like SQLCipher encryption with a simple config change. For plugin authors, it opens the door to offering flexible, modular plugins without maintaining separate packages.
If you want to learn more about encrypting SQLite databases with the SQLCipher trait, check out the guide on Encrypting SQLite databases in Capacitor.
If you have questions or want to share how you're using traits in your plugins, join the Capawesome Discord server. And subscribe to the Capawesome newsletter to stay up to date on the latest Capacitor news and plugin releases.