Choosing Between Capacitor Native Plugins and Cordova Plugins: A Decision Guide
A decision guide for choosing between Capacitor native plugins and legacy Cordova plugins. Covers constraints, a comparison table, trade-offs, and a concrete validation workflow using @capacitor/camera on iOS and Android.
13 Sept 2025, 10:38 UTC

The Decision and Constraints
When a Capacitor project needs device capabilities—camera, geolocation, Bluetooth, secure storage—you face a recurring choice: use a maintained Capacitor plugin or fall back to a legacy Cordova plugin through the compatibility layer. The decision is rarely about API surface; it is about maintenance burden, upgrade velocity, and how much native code your team is willing to own.
Constraints that drive the choice:
- Capacitor version target. Plugin APIs and native project templates shift between major versions (v4, v5, v6). A plugin that compiles on v5 may break on v6.
- Team native expertise. Capacitor treats the iOS and Android projects as committed source. Adding or debugging a native plugin means reading Swift/Objective-C or Kotlin/Java.
- CI/CD reproducibility. The
cap syncstep regenerates parts of the native projects. Manual native edits that live in generated files will be lost on the next sync. - Plugin maintenance cadence. Your app’s ability to move to the next Capacitor major version is gated by the slowest plugin you depend on.
Supported Options Compared
| Factor | Capacitor Native Plugin | Cordova Plugin (via Compatibility Layer) |
|---|---|---|
| Registration | Native class registered with Capacitor bridge (CAPPlugin on iOS, annotated class on Android) |
Cordova plugin.xml parsed by compatibility shim; may require pod install or Gradle edits |
| Build integration | Declared in Package.swift/CocoaPods and build.gradle; compiled with the app |
Often pulls binary frameworks or legacy Gradle configs that conflict with Capacitor’s generated project |
| JavaScript API | Promise-returning methods, typed via TypeScript definitions | Callback-based or Promise-wrapped; types often missing or outdated |
| Upgrade friction | Plugin maintainer updates for new Capacitor majors; usually same-day or weeks | Depends on Cordova plugin author; many unmaintained since 2020–2022 |
| Native debugging | Set breakpoints in Xcode/Android Studio directly in plugin source | Possible but harder; plugin source may be in a separate repo or binary-only |
| Web fallback | Often provides a no-op or mock implementation for browser dev | Rare; usually crashes or throws in browser context |
Trade-offs in Practice
Prefer Capacitor Native Plugins When
- A maintained Capacitor plugin exists for the capability (e.g.,
@capacitor/camera,@capacitor/geolocation,@capacitor/preferences). - Your team can read and modify Swift/Kotlin to fix edge cases or add small features.
- You need reliable TypeScript definitions and browser mocks for local development.
- You plan to upgrade Capacitor majors within the next 12 months.
Accept a Cordova Plugin Only When
- No maintained Capacitor equivalent exists (e.g., a niche Bluetooth LE profile, a proprietary scanner SDK).
- The Cordova plugin is actively maintained and declares Capacitor compatibility in its
package.json("capacitor": ">=4"). - You can allocate time to fork and maintain the plugin yourself if the upstream abandons it.
Rule of thumb: Every Cordova plugin you adopt becomes a line item in your Capacitor upgrade checklist. If you have three Cordova plugins, you have three external dependencies that must all be compatible before you can run npm install @capacitor/cli@next.
Concrete Validation: Adding a Camera Capability
This walkthrough shows how to validate the Capacitor-native path for camera access on a Capacitor v6 project targeting iOS 16+ and Android API 33+.
1. Install the Official Plugin
# Run in the Capacitor project root (where capacitor.config.ts lives)
npm install @capacitor/camera
npx cap sync
Permissions: Requires write access to node_modules and the native project directories (ios/App, android/app). No elevated privileges.
2. Verify Native Registration
After sync, open the native projects and confirm the plugin is linked:
- iOS: In Xcode, check
Pods/Development Pods/CapacitorCameraexists andCAPCameraPlugin.mappears in theCompile Sourcesbuild phase. - Android: In Android Studio, open
android/app/build.gradleand confirmimplementation project(":capacitor-camera")is present. The plugin classCameraPluginshould be inandroid/capacitor-camera/src/main/java/com/capacitorjs/plugins/camera/.
Risk: If you previously edited ios/App/App.xcodeproj/project.pbxproj or android/app/build.gradle manually, cap sync may have overwritten those edits. Keep manual native changes in ios/App/App/ source files or android/app/src/main/, not in the generated project configuration files.
3. Exercise the Plugin on Device
Create a minimal test component to verify the Promise-based API and permission flow:
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
async function takePhoto() {
const image = await Camera.getPhoto({
quality: 90,
allowEditing: false,
resultType: CameraResultType.Uri,
source: CameraSource.Camera
});
console.log('Photo URI:', image.webPath);
return image.webPath;
}
Run on a physical device (camera is unavailable in most simulators/emulators):
npx cap run ios --device
# or
npx cap run android --device
Expected checks:
- Permission dialog appears on first call.
- Returns a capacitor:// or file:// URI usable in an <img> tag.
- No TypeScript errors in the IDE.
- No native crashes in Xcode logcat or Android Logcat.
4. Confirm Browser Fallback Works
Start the dev server and open in a desktop browser:
npm run dev # or your framework's dev command
The same takePhoto() call should either return a mock blob (if the plugin provides a web implementation) or throw a clear Unavailable error that you can catch and handle gracefully. This keeps your local development loop fast without a device.
5. Reproducibility Check
Simulate a fresh checkout to ensure CI and new team members get the same result:
rm -rf node_modules ios/App/Pods android/app/.gradle
npm ci
npx cap sync
# Build both platforms in their IDEs or via CLI:
npx cap build ios
npx cap build android
Both builds must succeed without manual intervention. If a Cordova plugin were used, this step often fails due to missing pod install or Gradle version mismatches.
Limitations and When to Re-evaluate
- Niche hardware. If you need a proprietary barcode scanner SDK that only ships a Cordova plugin, you may have no choice. Wrap it in a thin Capacitor plugin facade to isolate the compatibility layer.
- Plugin abandonment. A Capacitor plugin can become unmaintained too. Monitor the repo’s issue response time and last release date before committing.
- Native code ownership. If your team has zero iOS/Android capacity, even Capacitor native plugins become a risk when you need to debug a native crash. Budget for at least one developer who can open Xcode and Android Studio.
Practical Verification Checklist
- Run
npx cap syncand build both native projects in their IDEs—confirm zero linker/Gradle errors. - Inspect generated registration: iOS
CAPPluginRegistryentries, AndroidPluginHandleentries. - Exercise every plugin method on a real device; browser testing does not cover WebView quirks.
- Repeat from a clean checkout to prove reproducibility for CI and onboarding.
If all four steps pass, the plugin choice is validated for the current Capacitor major version. Re-run this checklist before every Capacitor major upgrade.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.