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: (or , , etc.)
- Sync to native projects:
- 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:
bash
npx cap run ios
npx cap run android
- Open native IDE when native changes are needed:
bash
npx cap open ios # opens Xcode
npx cap open android # opens Android Studio
- Build a release binary:
bash
npx cap build android # outputs signed AAB/APK
npx cap build ios # outputs IPA
Keep
,
, and
on identical versions at all times. Update all together:
bash
npm i @capacitor/core @capacitor/ios @capacitor/android
npm i -D @capacitor/cli
Configuration
Capacitor-level settings live in
(or
). This file controls tooling only — not native runtime behavior. Key fields:
| Field | Purpose |
|---|
| Bundle identifier (e.g. ) |
| Display name |
| Output directory of the web build (e.g. , , ) |
| Override for live reload (remove before committing) |
Platform-specific runtime configuration is done in native IDEs:
and
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 () — maintained by the Capacitor team, covering Camera, Filesystem, Geolocation, Push Notifications, Preferences, etc.
- Community plugins () — curated third-party ecosystem
- Cordova plugins — compatibility layer; prefer Capacitor-native equivalents
Install a plugin, then sync:
bash
npm install @capacitor/camera
npx cap sync
Always check whether a plugin requires additional native configuration (entitlements on iOS, permissions in
). 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
:
ts
// __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
before
.
Jasmine/Angular — add a
mapping in
:
json
"paths": {
"@capacitor/*": ["__mocks__/@capacitor/*"]
}
Note:
in
replaces (does not merge with) the base
paths — include all existing entries.
Storage
Do not rely on
or
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) | |
| Large datasets / SQL queries | SQLite plugin () |
| Sensitive values (encryption keys, auth tokens) | iOS Keychain / Android Keystore via a plugin |
ts
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' });
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
or
unencrypted.
Network
Only make requests to
endpoints. Never use
in production builds.
Content Security Policy
Add a CSP
tag in
to restrict resource loading:
html
<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.
) 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):
ts
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:
- Configure the native app:
- iOS: enable Associated Domains in Xcode ()
- Android: add with
android:autoVerify="true"
in
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):
bash
npm install -g @ionic/cli native-run
ionic cap run ios -l --external
ionic cap run android -l --external
Without Ionic CLI — add a
entry to
:
json
"server": {
"url": "http://192.168.1.68:8100",
"cleartext": true
}
Run
to push the config update, then launch from the native IDE.
Never commit the config to source control. Remove it before building for release.
Quick Reference
| Task | Command |
|---|
| Sync web build to native | |
| Copy web assets only | |
| Open iOS project | |
| Open Android project | |
| Run on device | / |
| Build release binary | / |
| 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 detail
references/security-and-deeplinks.md
— CSP patterns, Keychain/Keystore guidance, full Universal Links / App Links file formats, PKCE requirements