Capacitor Expert
Comprehensive reference for building cross-platform apps with Capacitor. Covers architecture, CLI, plugins, framework integration, best practices, and Capawesome Cloud.
Core Concepts
Capacitor is a cross-platform native runtime for building web apps that run natively on iOS, Android, and the web. The web app runs in a native WebView, and Capacitor provides a bridge to native APIs via plugins.
Architecture
A Capacitor app has three layers:
- Web layer -- HTML/CSS/JS app running inside a native WebView (WKWebView on iOS, Android System WebView on Android).
- Native bridge -- Serializes JS plugin calls, routes them to native code, and returns results as Promises.
- Native layer -- Swift/ObjC (iOS) and Kotlin/Java (Android) code implementing native functionality.
Data passed across the bridge must be JSON-serializable. Pass files as paths, not base64.
Project Structure
my-app/
android/ # Native Android project (committed to VCS)
ios/ # Native iOS project (committed to VCS)
App/
App/ # iOS app source files
App.xcodeproj/
src/ # Web app source code
dist/ or www/ or build/ # Built web assets
capacitor.config.ts # Capacitor configuration
package.json
The
and
directories are full native projects -- they are committed to version control and can be modified directly.
Capacitor Config
(preferred) or
controls app behavior:
typescript
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'My App',
webDir: 'dist',
server: {
// androidScheme: 'https', // default in Cap 6+
},
};
export default config;
For details, see
App Configuration.
Creating a New App
Quick Start
bash
# 1. Create a web app (React example with Vite)
npm create vite@latest my-app -- --template react-ts
cd my-app && npm install
# 2. Install Capacitor
npm install @capacitor/core
npm install -D @capacitor/cli
# 3. Initialize Capacitor
npx cap init "My App" com.example.myapp --web-dir dist
# 4. Build web assets
npm run build
# 5. Add platforms
npm install @capacitor/android @capacitor/ios
npx cap add android
npx cap add ios
# 6. Sync and run
npx cap sync
npx cap run android
npx cap run ios
Web asset directories by framework:
- Angular:
dist/<project-name>/browser
(Angular 17+ with application builder)
- React (Vite):
- Vue (Vite):
- Vanilla:
For the full guided creation flow, see
capacitor-app-creation.
Capacitor CLI
All commands:
. Most important commands:
| Command | Purpose |
|---|
| Initialize Capacitor in a project |
| Add Android or iOS platform |
| Copy web assets + update native dependencies (run after every plugin install, config change, or web build) |
| Copy web assets only (faster, no native dependency update) |
| Build, sync, and deploy to device/emulator |
npx cap run <platform> -l --external
| Run with live reload |
| Open native project in IDE |
| Build native project |
| Diagnose configuration issues |
| List installed plugins |
| Automated upgrade to newer Capacitor version |
For the full CLI reference, see
CLI Reference.
Framework Integration
Capacitor works with any web framework. Framework-specific patterns:
Angular
- Wrap Capacitor plugins in Angular services for DI and testability.
- Plugin event listeners run outside NgZone -- always wrap callbacks in .
- Register listeners in , remove in .
For details, see
capacitor-angular.
React
- Create custom hooks (, ) that wrap Capacitor plugins.
- Use for listener registration with cleanup to prevent memory leaks.
- React 18 strict mode double-mounts -- ensure cleanup functions work correctly.
For details, see
capacitor-react.
Vue
- Create composables (, ) using Vue 3 Composition API.
- Register listeners in , remove in .
- Vue reactivity picks up changes automatically (no NgZone equivalent needed).
For details, see
capacitor-vue.
Plugins
Plugins are Capacitor's extension mechanism. Each plugin exposes a JS API backed by native implementations.
Plugin Sources
- Official () -- Camera, Filesystem, Geolocation, Preferences, etc.
- Capawesome (, ) -- SQLite, NFC, Biometrics, Live Update, etc.
- Community () -- AdMob, BLE, SQLite, Stripe, etc.
- Firebase () -- Analytics, Auth, Messaging, Firestore, etc.
- MLKit () -- Barcode scanning, face detection, translation.
- RevenueCat (
@revenuecat/purchases-capacitor
) -- In-app purchases.
Installing a Plugin
bash
npm install @capacitor/camera
npx cap sync
After installation, apply any required platform configuration (permissions in
,
entries, etc.) as documented by the plugin.
Using a Plugin
typescript
import { Camera, CameraResultType } from '@capacitor/camera';
const photo = await Camera.getPhoto({
quality: 90,
resultType: CameraResultType.Uri,
});
For the full plugin index (160+ plugins) and setup guides, see
capacitor-plugins.
Plugin Development
Create custom Capacitor plugins with native iOS (Swift) and Android (Java/Kotlin) implementations:
- Scaffold with
npm init @capacitor/plugin@latest
.
- Define the TypeScript API in .
- Implement the web layer in .
- Implement iOS plugin in .
- Implement Android plugin in .
- Verify with .
Key rules:
- The name in must match on iOS and
@CapacitorPlugin(name = "...")
on Android.
- iOS methods need and must be listed in (CAPBridgedPlugin).
- Android methods need annotation and must be .
Cross-Platform Best Practices
Platform Detection
typescript
import { Capacitor } from '@capacitor/core';
const platform = Capacitor.getPlatform(); // 'android' | 'ios' | 'web'
if (Capacitor.isNativePlatform()) { /* native-only code */ }
if (Capacitor.isPluginAvailable('Camera')) { /* plugin available */ }
Permissions
Follow the check-then-request pattern:
typescript
const status = await Camera.checkPermissions();
if (status.camera !== 'granted') {
const requested = await Camera.requestPermissions();
if (requested.camera === 'denied') {
// Guide user to app settings -- cannot re-request on iOS
return;
}
}
const photo = await Camera.getPhoto({ ... });
Performance
- Minimize bridge calls -- batch operations instead of many individual calls.
- Use file paths over base64 for binary data.
- Lazy-load plugins with dynamic imports for code splitting.
Error Handling
Always wrap plugin calls in try-catch:
typescript
try {
const photo = await Camera.getPhoto({ resultType: CameraResultType.Uri });
} catch (error) {
if (error.message === 'User cancelled photos app') {
// Not an error
} else {
console.error('Camera error:', error);
}
}
Deep Links
Deep links open specific content in the app from external URLs.
- iOS: Universal Links via
apple-app-site-association
hosted at https://<domain>/.well-known/
.
- Android: App Links via hosted at
https://<domain>/.well-known/
.
Listener Setup
typescript
import { App } from '@capacitor/app';
App.addListener('appUrlOpen', (event) => {
const path = new URL(event.url).pathname;
// Route to the appropriate page
});
Platform Configuration
- iOS: Add to Associated Domains capability in
ios/App/App/App.entitlements
.
- Android: Add
<intent-filter android:autoVerify="true">
to android/app/src/main/AndroidManifest.xml
.
For full setup, see
Deep Links.
Storage
| Requirement | Solution |
|---|
| App settings, preferences | (native key-value, persists reliably) |
| Sensitive data (tokens, credentials) | @capawesome-team/capacitor-secure-preferences
(Keychain/Keystore) |
| Relational data, offline-first | SQLite (@capawesome-team/capacitor-sqlite
or @capacitor-community/sqlite
) |
| Files, images, documents | |
Do NOT use ,
, or cookies for persistent data -- the OS can evict them (especially on iOS).
For details, see
Storage.
Security
- Never embed secrets (API keys with write access, OAuth secrets, DB credentials) in client code -- move to a server API.
- Use secure storage (
@capawesome-team/capacitor-secure-preferences
) for tokens and credentials, not or .
- HTTPS only -- never allow cleartext HTTP in production.
- Content Security Policy -- add a CSP tag in .
- Disable WebView debugging in production: set
webContentsDebuggingEnabled: false
in .
- Prefer Universal/App Links over custom URL schemes (verified via HTTPS).
- iOS Privacy Manifest () -- required for iOS 17+ when using privacy-sensitive APIs.
For details, see
Security.
Testing
Unit Testing
Mock Capacitor plugins in Jest/Vitest since tests run in Node.js, not a WebView:
typescript
vi.mock('@capacitor/camera', () => ({
Camera: {
getPhoto: vi.fn().mockResolvedValue({
webPath: 'https://example.com/photo.jpg',
}),
},
}));
E2E Testing
- Web E2E: Cypress or Playwright (tests web layer, plugins must be mocked).
- Native E2E: Appium (cross-platform) or Detox (iOS-focused).
Debugging
- Android: Enable
webContentsDebuggingEnabled: true
, open in Chrome.
- iOS: Enable
webContentsDebuggingEnabled: true
, use Safari > Develop menu > select device.
For details, see
Testing.
Troubleshooting
Android
- fails: Verify and versions match. Run
cd android && ./gradlew clean
.
- Build fails after config changes: Clean with
cd android && ./gradlew clean
, then rebuild.
- Plugin not found at runtime: Run after plugin installation. Verify Gradle sync completed.
- SDK errors: Verify is set. Install missing SDK versions via Android Studio SDK Manager.
- White square notification icon: Push notification icons must be white pixels on transparent background.
iOS
- Build fails with "no such module": Run . For CocoaPods:
cd ios/App && pod install --repo-update
.
- Build fails after config changes: Clean build folder (Xcode Product > Clean Build Folder) or delete and re-run .
- Simulator cannot receive push notifications: Use a physical device for push notification testing.
- Permission denied permanently: Cannot re-request on iOS. Guide user to Settings > App > Permissions.
- WebView not loading: Verify in matches the actual build output directory.
General
- Live reload not connecting: Ensure device and dev machine are on the same network. Use flag.
- Plugin not found: Run . Verify plugin is in dependencies.
- : Install (
npm install @capacitor/core
).
Upgrading
Capacitor supports upgrades across major versions (4 through 8). Apply each major version jump sequentially -- do not skip intermediate versions.
| Current to Target | Node.js | Xcode | Android Studio |
|---|
| to 5 | 16+ | 14.1+ | Flamingo 2022.2.1+ |
| to 6 | 18+ | 15.0+ | Hedgehog 2023.1.1+ |
| to 7 | 20+ | 16.0+ | Ladybug 2024.2.1+ |
| to 8 | 22+ | 26.0+ | Otter 2025.2.1+ |
Quick automated upgrade:
bash
npx cap migrate
npx cap sync
If
fails partially, apply manual steps from the upgrade guides.
For app upgrades, see
capacitor-app-upgrades.
For plugin upgrades, see
capacitor-plugin-upgrades.
Capawesome Cloud
Capawesome Cloud provides cloud infrastructure for Capacitor apps: native builds, live updates, and automated app store publishing.
Getting Started
bash
# Install and authenticate
npx @capawesome/cli login
# Create an app
npx @capawesome/cli apps:create
Live Updates
Deploy over-the-air (OTA) web updates to Capacitor apps without going through the app stores. Users receive updates immediately on next app launch.
Setup:
bash
# Install the live update plugin
npm install @capawesome/capacitor-live-update
npx cap sync
typescript
const config: CapacitorConfig = {
plugins: {
LiveUpdate: {
appId: '<APP_ID>',
autoUpdate: true,
},
},
};
Deploy an update:
bash
npm run build
npx @capawesome/cli apps:liveupdates:upload --app-id <APP_ID>
Native Builds
Build iOS and Android apps in the cloud without local build environments. Supports signing certificates, environments, and build configuration.
bash
# Trigger a build
npx @capawesome/cli apps:builds:create --app-id <APP_ID> --platform android
# Download the artifact
npx @capawesome/cli apps:builds:download --app-id <APP_ID> --build-id <BUILD_ID>
App Store Publishing
Automate submissions to Apple App Store (TestFlight) and Google Play Store.
bash
# Create a deployment destination
npx @capawesome/cli apps:destinations:create --app-id <APP_ID>
# Deploy a build
npx @capawesome/cli apps:deployments:create --app-id <APP_ID> --build-id <BUILD_ID>
CI/CD Integration
Use token-based auth for CI/CD pipelines:
bash
npx @capawesome/cli login --token <TOKEN>
npx @capawesome/cli apps:builds:create --app-id <APP_ID> --platform ios --detached
For full Capawesome Cloud setup, see
capawesome-cloud.
For the Capawesome CLI reference, see
capawesome-cli.
Push Notifications
Set up push notifications using Firebase Cloud Messaging (FCM) via
@capacitor-firebase/messaging
:
bash
npm install @capacitor-firebase/messaging firebase
npx cap sync
Requires Firebase project setup, platform-specific configuration (APNs for iOS,
for Android), and permission handling.
For the full setup guide, see
capacitor-push-notifications.
In-App Purchases
Set up in-app purchases and subscriptions with either:
- Capawesome Purchases (
@capawesome-team/capacitor-purchases
) -- lightweight, no third-party backend, requires Capawesome Insiders license.
- RevenueCat (
@revenuecat/purchases-capacitor
) -- full managed backend with receipt validation, analytics, and integrations.
Both require App Store Connect (iOS) and/or Google Play Console (Android) product configuration.
For the full setup guide, see
capacitor-in-app-purchases.
Related Skills
- capacitor-app-creation -- Create a new Capacitor app from scratch.
- capacitor-app-development -- General Capacitor development topics, configuration, and troubleshooting.
- capacitor-angular -- Angular-specific Capacitor patterns.
- capacitor-react -- React-specific Capacitor patterns.
- capacitor-vue -- Vue-specific Capacitor patterns.
- capacitor-plugins -- Install and configure 160+ Capacitor plugins.
- capacitor-plugin-development -- Create custom Capacitor plugins.
- capacitor-app-upgrades -- Upgrade Capacitor apps across major versions.
- capacitor-plugin-upgrades -- Upgrade Capacitor plugins across major versions.
- capacitor-push-notifications -- Push notifications with FCM.
- capacitor-in-app-purchases -- In-app purchases and subscriptions.
- capawesome-cloud -- Live updates, native builds, app store publishing.
- capawesome-cli -- Capawesome CLI reference.