capacitor-best-practices
cap-go/capgo-skills
Best practices for building production-ready Capacitor apps: structure, plugins, performance, security, and deployment.
What is capacitor-best-practices?
Comprehensive guidelines for Capacitor app development covering project structure, plugin usage, performance optimization, security measures, and deployment strategies. Use this skill when setting up new Capacitor projects, reviewing app architecture, optimizing performance, or preparing for app store submission.
- Establish recommended project directory structure and configuration patterns
- Manage plugin installation, initialization, and availability checks to prevent crashes
- Optimize performance through lazy loading, bridge call batching, and image compression
- Implement security best practices including secure storage, certificate pinning, and root/jailbreak detection
- Handle plugin errors gracefully with proper try-catch patterns and user feedback
- Configure live updates using Capacitor Updater for seamless app maintenance
How to install capacitor-best-practices
npx skills add https://github.com/cap-go/capgo-skills --skill capacitor-best-practices- Node.js and npm installed
- Existing Capacitor project or familiarity with @capacitor/cli
- Basic understanding of TypeScript and async/await patterns
How to use capacitor-best-practices
- 1.Review the recommended project structure and organize your codebase accordingly
- 2.Update capacitor.config.ts to match the provided configuration pattern, ensuring development URLs are environment-gated
- 3.When installing plugins, follow the three-step pattern: npm install, npx cap sync, and platform-specific setup (pod install for iOS)
- 4.Refactor plugin imports to use dynamic imports for lazy loading, especially for heavy plugins
- 5.Batch storage operations and minimize bridge calls by grouping related data updates
- 6.Implement error handling for all plugin calls with specific error message checks
- 7.Enable hardware acceleration in Android and optimize WebView settings in iOS
- 8.For sensitive data, use NativeBiometric or secure storage instead of plain Preferences
Use cases
- Setting up a new Capacitor project with proper structure and configuration
- Reviewing existing Capacitor code for security vulnerabilities and performance issues
- Optimizing app startup time by lazy-loading plugins and minimizing bridge calls
- Implementing biometric authentication with fallback mechanisms
- Preparing an app for iOS App Store and Google Play Store submission with security hardening
- Mobile app developers building cross-platform apps with Capacitor
- Full-stack developers transitioning from web to native mobile development
- DevOps engineers managing Capacitor app deployment and live updates
- Security-focused developers implementing authentication and secure data storage
capacitor-best-practices FAQ
The sync command links native code from the plugin to your Android and iOS projects. Without it, the native functionality won't be available and your app will crash when calling the plugin.
No. Preferences stores data in plain text. Always use NativeBiometric.setCredentials() or platform-specific secure storage (Keychain on iOS, Keystore on Android) for sensitive data like passwords and tokens.
Always call the plugin's availability check method first (e.g., NativeBiometric.isAvailable()) and provide a fallback implementation if the feature isn't supported on that device.
Each plugin increases your initial bundle size and startup time. Use dynamic imports (await import()) to load plugins only when needed, significantly improving app launch performance.
No. Always gate development URLs behind environment checks (process.env.NODE_ENV === 'development'). Never commit cleartext: true or localhost URLs to production builds.
Full instructions (SKILL.md)
Source of truth, from cap-go/capgo-skills.
name: capacitor-best-practices description: Best practices for Capacitor app development including project structure, plugin usage, performance optimization, security, and deployment. Use this skill when reviewing Capacitor code, setting up new projects, or optimizing existing apps.
Capacitor Best Practices
Comprehensive guidelines for building production-ready Capacitor applications.
When to Use This Skill
- Setting up a new Capacitor project
- Reviewing Capacitor app architecture
- Optimizing app performance
- Implementing security measures
- Preparing for app store submission
Project Structure
Recommended Directory Layout
my-app/
├── src/ # Web app source
├── android/ # Android native project
├── ios/ # iOS native project
├── capacitor.config.ts # Capacitor configuration
├── package.json
└── tsconfig.json
Configuration Best Practices
capacitor.config.ts (CORRECT):
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.company.app',
appName: 'My App',
webDir: 'dist',
server: {
// Only enable for development
...(process.env.NODE_ENV === 'development' && {
url: 'http://localhost:5173',
cleartext: true,
}),
},
plugins: {
SplashScreen: {
launchAutoHide: false,
},
},
};
export default config;
capacitor.config.json (AVOID):
{
"server": {
"url": "http://localhost:5173",
"cleartext": true
}
}
Never commit development server URLs to production
Plugin Usage
CRITICAL: Always Use Latest Capacitor
Keep Capacitor core packages in sync:
npm install @capacitor/core@latest @capacitor/cli@latest
npm install @capacitor/ios@latest @capacitor/android@latest
npx cap sync
Plugin Installation Pattern
CORRECT:
# 1. Install the package
npm install @capgo/capacitor-native-biometric
# 2. Sync native projects
npx cap sync
# 3. For iOS: Install pods (or use SPM)
cd ios/App && pod install && cd ../..
INCORRECT:
# Missing sync step
npm install @capgo/capacitor-native-biometric
# App crashes because native code not linked
Plugin Initialization
CORRECT - Check availability before use:
import { NativeBiometric, BiometryType } from '@capgo/capacitor-native-biometric';
async function authenticate() {
const { isAvailable, biometryType } = await NativeBiometric.isAvailable();
if (!isAvailable) {
// Fallback to password
return authenticateWithPassword();
}
try {
await NativeBiometric.verifyIdentity({
reason: 'Authenticate to access your account',
title: 'Biometric Login',
});
return true;
} catch (error) {
// User cancelled or biometric failed
return false;
}
}
INCORRECT - No availability check:
// Will crash if biometrics not available
await NativeBiometric.verifyIdentity({ reason: 'Login' });
Performance Optimization
CRITICAL: Lazy Load Plugins
CORRECT - Dynamic imports:
// Only load when needed
async function scanDocument() {
const { DocumentScanner } = await import('@capgo/capacitor-document-scanner');
return DocumentScanner.scanDocument();
}
INCORRECT - Import everything at startup:
// Increases initial bundle size
import { DocumentScanner } from '@capgo/capacitor-document-scanner';
import { NativeBiometric } from '@capgo/capacitor-native-biometric';
import { Camera } from '@capacitor/camera';
// ... 20 more plugins
HIGH: Optimize WebView Performance
CORRECT - Use hardware acceleration:
<!-- android/app/src/main/AndroidManifest.xml -->
<application
android:hardwareAccelerated="true"
android:largeHeap="true">
<!-- ios/App/App/Info.plist -->
<key>UIViewGroupOpacity</key>
<false/>
HIGH: Minimize Bridge Calls
CORRECT - Batch operations:
// Single call with batch data
await Storage.set({
key: 'userData',
value: JSON.stringify({ name, email, preferences }),
});
INCORRECT - Multiple bridge calls:
// Each call crosses the JS-native bridge
await Storage.set({ key: 'name', value: name });
await Storage.set({ key: 'email', value: email });
await Storage.set({ key: 'preferences', value: JSON.stringify(preferences) });
MEDIUM: Image Optimization
CORRECT:
import { Camera, CameraResultType } from '@capacitor/camera';
const photo = await Camera.getPhoto({
quality: 80, // Not 100
width: 1024, // Reasonable max
resultType: CameraResultType.Uri, // Not Base64 for large images
correctOrientation: true,
});
INCORRECT:
const photo = await Camera.getPhoto({
quality: 100,
resultType: CameraResultType.Base64, // Memory intensive
// No size limits
});
Security Best Practices
CRITICAL: Secure Storage
CORRECT - Use secure storage for sensitive data:
import { NativeBiometric } from '@capgo/capacitor-native-biometric';
// Store credentials securely
await NativeBiometric.setCredentials({
username: 'user@example.com',
password: 'secret',
server: 'api.myapp.com',
});
// Retrieve with biometric verification
const credentials = await NativeBiometric.getCredentials({
server: 'api.myapp.com',
});
INCORRECT - Plain storage:
import { Preferences } from '@capacitor/preferences';
// NEVER store sensitive data in plain preferences
await Preferences.set({
key: 'password',
value: 'secret', // Stored in plain text!
});
CRITICAL: Certificate Pinning
For production apps handling sensitive data:
// capacitor.config.ts
const config: CapacitorConfig = {
plugins: {
CapacitorHttp: {
enabled: true,
},
},
server: {
// Disable cleartext in production
cleartext: false,
},
};
HIGH: Root/Jailbreak Detection
import { IsRoot } from '@capgo/capacitor-is-root';
async function checkDeviceSecurity() {
const { isRooted } = await IsRoot.isRooted();
if (isRooted) {
// Show warning or restrict functionality
showSecurityWarning('Device appears to be rooted/jailbroken');
}
}
HIGH: App Tracking Transparency (iOS)
import { AppTrackingTransparency } from '@capgo/capacitor-app-tracking-transparency';
async function requestTracking() {
const { status } = await AppTrackingTransparency.requestPermission();
if (status === 'authorized') {
// Enable analytics
}
}
Error Handling
CRITICAL: Always Handle Plugin Errors
CORRECT:
import { Camera, CameraResultType } from '@capacitor/camera';
async function takePhoto() {
try {
const image = await Camera.getPhoto({
quality: 90,
resultType: CameraResultType.Uri,
});
return image;
} catch (error) {
if (error.message === 'User cancelled photos app') {
// User cancelled, not an error
return null;
}
if (error.message.includes('permission')) {
// Permission denied
showPermissionDialog();
return null;
}
// Unexpected error
console.error('Camera error:', error);
throw error;
}
}
INCORRECT:
// No error handling
const image = await Camera.getPhoto({ quality: 90 });
Live Updates
Using Capacitor Updater
import { CapacitorUpdater } from '@capgo/capacitor-updater';
// Notify when app is ready
CapacitorUpdater.notifyAppReady();
// Listen for updates
CapacitorUpdater.addListener('updateAvailable', async (update) => {
// Download in background
const bundle = await CapacitorUpdater.download({
url: update.url,
version: update.version,
});
// Apply on next app start
await CapacitorUpdater.set(bundle);
});
Update Strategy
CORRECT - Background download, apply on restart:
// Download silently
const bundle = await CapacitorUpdater.download({ url, version });
// User continues using app...
// Apply when they close/reopen
await CapacitorUpdater.set(bundle);
INCORRECT - Interrupt user:
// Don't force reload while user is active
const bundle = await CapacitorUpdater.download({ url, version });
await CapacitorUpdater.reload(); // Disrupts user
Native Project Management
iOS: Use Swift Package Manager (SPM)
Modern approach - prefer SPM over CocoaPods:
# Podfile - Remove plugin pods, use SPM instead
target 'App' do
capacitor_pods
# Plugin dependencies via SPM in Xcode
end
Android: Gradle Configuration
// android/app/build.gradle
android {
defaultConfig {
minSdkVersion 22
targetSdkVersion 34
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
Testing
Plugin Mocking
// Mock for web testing
jest.mock('@capgo/capacitor-native-biometric', () => ({
NativeBiometric: {
isAvailable: jest.fn().mockResolvedValue({
isAvailable: true,
biometryType: 'touchId',
}),
verifyIdentity: jest.fn().mockResolvedValue({}),
},
}));
Platform Detection
import { Capacitor } from '@capacitor/core';
if (Capacitor.isNativePlatform()) {
// Native-specific code
} else {
// Web fallback
}
// Or check specific platform
if (Capacitor.getPlatform() === 'ios') {
// iOS-specific code
}
Deployment Checklist
- Remove development server URLs from config
- Enable ProGuard for Android release builds
- Set appropriate iOS deployment target
- Test on real devices, not just simulators
- Verify all permissions are declared
- Test with poor network conditions
- Verify deep links work correctly
- Test app backgrounding/foregrounding
- Verify push notifications work
- Test biometric authentication edge cases
Resources
- Capacitor Documentation: https://capacitorjs.com/docs
- Capgo Documentation: https://capgo.app/docs
- Ionic Framework: https://ionicframework.com/docs
Related skills
More from cap-go/capgo-skills and the wider catalog.

capacitor-plugins
Official Capacitor packages plus Capgo ecosystem plugins for native functionality.

ionic-design
Build beautiful native-looking mobile apps with Ionic Framework components and Capacitor.

capacitor-plugins
Install and configure Capacitor plugins from official, Capawesome, community, Firebase, MLKit, and RevenueCat sources.

multi-search
智能多主题深度研究工具,支持任意材料输入,使用独立研究 Agent进行并行深度检索并生成系统化研究文档。本技能应在用户需要深度研究多个相关课题、进行系统性信息检索、整合多角度分析时使用。

datapackage
Explore and query Frictionless Data Package descriptors to discover tables, columns, and metadata without loading full datasets.

pudl
Explore and load PUDL energy data: discover tables, understand columns, and access Parquet files without the Python package.