capacitor
Capacitor: Cross-Platform Native Runtime
Capacitor is a cross-platform native runtime for building Web Native apps that run on iOS, Android, and the web from a single modern web codebase. It provides a consistent, web-focused API layer that bridges JavaScript to native device features via a plugin system.
Core Workflow
The canonical development loop is:
- Build web assets:
npm run build(orng build,vite build, etc.) - Sync to native projects:
npx cap sync- Copies the web bundle into iOS and Android native projects
- Installs/updates native plugin dependencies
- Run this after every web build or plugin change
- Test on device:
npx cap run ios npx cap run android - Open native IDE when native changes are needed:
npx cap open ios # opens Xcode npx cap open android # opens Android Studio - Build a release binary:
npx cap build android # outputs signed AAB/APK npx cap build ios # outputs IPA
Keep @capacitor/core, @capacitor/ios, and @capacitor/android on identical versions at all times. Update all together:
npm i @capacitor/core @capacitor/ios @capacitor/android
npm i -D @capacitor/cli
Configuration
Capacitor-level settings live in capacitor.config.ts (or .json). This file controls tooling only — not native runtime behavior. Key fields:
| Field | Purpose |
|---|---|
appId |
Bundle identifier (e.g. com.example.app) |
appName |
Display name |
webDir |
Output directory of the web build (e.g. dist, build, www) |
server.url |
Override for live reload (remove before committing) |
Platform-specific runtime configuration is done in native IDEs: ios/App/App/Info.plist and AndroidManifest.xml for Android. Do not attempt to manage those via the Capacitor config file.
Plugins
Capacitor plugins expose native APIs to JavaScript. Three sources exist:
- Official plugins (
@capacitor/*) — maintained by the Capacitor team, covering Camera, Filesystem, Geolocation, Push Notifications, Preferences, etc. - Community plugins (
@capacitor-community/*) — curated third-party ecosystem - Cordova plugins — compatibility layer; prefer Capacitor-native equivalents
Install a plugin, then sync:
npm install @capacitor/camera
npx cap sync
Always check whether a plugin requires additional native configuration (entitlements on iOS, permissions in AndroidManifest.xml). The plugin's README will specify.
Mocking Plugins in Tests
Capacitor plugins are JavaScript proxies; wrapping a proxy in another proxy fails. Use manual mocks instead.
Jest — place stubs under __mocks__/@capacitor/:
// __mocks__/@capacitor/preferences.ts
export const Preferences = {
async get(data: { key: string }) { return { value: undefined }; },
async set(_data: { key: string; value: string }) {},
async remove(_data: { key: string }) {},
async clear() {},
};
Jest auto-discovers files from __mocks__/ before node_modules.
Jasmine/Angular — add a paths mapping in tsconfig.spec.json:
"paths": {
"@capacitor/*": ["__mocks__/@capacitor/*"]
}
Note: paths in tsconfig.spec.json replaces (does not merge with) the base tsconfig.json paths — include all existing entries.
Storage
Do not rely on localStorage or IndexedDB as primary storage on mobile. The OS reclaims both when storage is low. IndexedDB on iOS is not persisted by default.
| Use case | Recommended solution |
|---|---|
| Small key/value data (settings, tokens) | @capacitor/preferences |
| Large datasets / SQL queries | SQLite plugin (capacitor-sqlite) |
| Sensitive values (encryption keys, auth tokens) | iOS Keychain / Android Keystore via a plugin |
import { Preferences } from '@capacitor/preferences';
// Write
await Preferences.set({ key: 'theme', value: 'dark' });
// Read
const { value } = await Preferences.get({ key: 'theme' });
// Remove
await Preferences.remove({ key: 'theme' });
Preferences values are stored natively and survive app updates and low-storage eviction.
Security
Secrets and API Keys
Never embed secrets in web app code. The JavaScript bundle ships inside the app binary and is trivially extractable. Move secret-dependent logic server-side (serverless function or backend API). If a token must exist on device (e.g. auth token), store it only in memory or in the native Keychain/Keystore — never in localStorage or Preferences unencrypted.
Network
Only make requests to https:// endpoints. Never use http:// in production builds.
Content Security Policy
Add a CSP <meta> tag in index.html to restrict resource loading:
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; connect-src 'self' https://api.example.com"
/>
oAuth2 / Deep Link Authentication
Custom URL schemes (e.g. myapp://) are not domain-controlled and can be intercepted by a malicious app. Never pass sensitive tokens through a custom scheme. Use Universal Links (iOS) or App Links (Android) for auth callbacks, and always enable PKCE in oAuth2 flows.
Deep Links (Universal Links / App Links)
Deep links allow HTTPS URLs to open specific screens inside the native app. Prefer Universal/App Links over custom URL schemes — they require verified web domain ownership.
Listen for Incoming Links
Register the listener early (e.g. app bootstrap):
import { App } from '@capacitor/app';
App.addListener('appUrlOpen', (event) => {
const path = new URL(event.url).pathname;
// hand off to your router
router.navigate(path);
});
Required Setup (both platforms)
- Host a site association file at
https://yourdomain.com/.well-known/:- iOS:
apple-app-site-association(no extension, JSON) - Android:
assetlinks.json
- iOS:
- Configure the native app:
- iOS: enable Associated Domains in Xcode (
applinks:yourdomain.com) - Android: add
intent-filterwithandroid:autoVerify="true"inAndroidManifest.xml
- iOS: enable Associated Domains in Xcode (
See references/security-and-deeplinks.md for complete file formats and intent filter XML.
Live Reload (Development Only)
Live reload reloads the WebView when web source files change — no native rebuild required.
With Ionic CLI (recommended):
npm install -g @ionic/cli native-run
ionic cap run ios -l --external
ionic cap run android -l --external
Without Ionic CLI — add a server entry to capacitor.config.json:
"server": {
"url": "http://192.168.1.68:8100",
"cleartext": true
}
Run npx cap copy to push the config update, then launch from the native IDE.
Never commit the server config to source control. Remove it before building for release.
Quick Reference
| Task | Command |
|---|---|
| Sync web build to native | npx cap sync |
| Copy web assets only | npx cap copy |
| Open iOS project | npx cap open ios |
| Open Android project | npx cap open android |
| Run on device | npx cap run ios / npx cap run android |
| Build release binary | npx cap build ios / npx cap build android |
| Update Capacitor | npm i @capacitor/core @capacitor/ios @capacitor/android |
Additional Resources
references/plugins-and-storage.md— Plugin installation patterns, Preferences API reference, SQLite options, and testing stubs in detailreferences/security-and-deeplinks.md— CSP patterns, Keychain/Keystore guidance, full Universal Links / App Links file formats, PKCE requirements