plugins/capacitor-core/skills/capacitor-best-practices/SKILL.md
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.
npx skillsauth add cap-go/capgo-skills capacitor-best-practicesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Comprehensive guidelines for building production-ready Capacitor applications.
my-app/
├── src/ # Web app source
├── android/ # Android native project
├── ios/ # iOS native project
├── capacitor.config.ts # Capacitor configuration
├── package.json
└── tsconfig.json
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
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
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
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' });
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
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/>
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) });
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
});
CORRECT - Use secure storage for sensitive data:
import { NativeBiometric } from '@capgo/capacitor-native-biometric';
// Store credentials securely
await NativeBiometric.setCredentials({
username: '[email protected]',
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!
});
For production apps handling sensitive data:
// capacitor.config.ts
const config: CapacitorConfig = {
plugins: {
CapacitorHttp: {
enabled: true,
},
},
server: {
// Disable cleartext in production
cleartext: false,
},
};
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');
}
}
import { AppTrackingTransparency } from '@capgo/capacitor-app-tracking-transparency';
async function requestTracking() {
const { status } = await AppTrackingTransparency.requestPermission();
if (status === 'authorized') {
// Enable analytics
}
}
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 });
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);
});
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
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/app/build.gradle
android {
defaultConfig {
minSdkVersion 22
targetSdkVersion 34
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
// Mock for web testing
jest.mock('@capgo/capacitor-native-biometric', () => ({
NativeBiometric: {
isAvailable: jest.fn().mockResolvedValue({
isAvailable: true,
biometryType: 'touchId',
}),
verifyIdentity: jest.fn().mockResolvedValue({}),
},
}));
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
}
development
Guide for migrating an existing web app, PWA, or SPA into a store-ready Capacitor iOS and Android app. Use this skill when users want to wrap or convert a web app into a mobile app, avoid thin WebView app store rejection, add native-feeling UX, handle permissions, offline behavior, account deletion, billing, testing, and Capgo live updates.
development
Guide to using Tailwind CSS in Capacitor mobile apps. Covers mobile-first design, touch targets, safe areas, dark mode, and performance optimization. Use this skill when users want to style Capacitor apps with Tailwind.
development
Revenue playbook for getting a mobile or web subscription app from zero to early MRR. Use when users ask how to make revenue, reach $1K MRR, monetize an app, get first users, improve ASO, plan TikTok/Reels/Shorts or Reddit acquisition, design a paywall, choose freemium vs trial, price subscriptions, reduce churn, or build a simple growth loop for an app.
tools
Guides the agent through migrating SQLite and SQL-style Capacitor plugins to @capgo/capacitor-fast-sql. Use when replacing bridge-based SQL plugins, adding encryption, preserving transactions, or moving key-value storage onto Fast SQL. Do not use for non-SQL storage, generic app upgrades, or plugins that already wrap Fast SQL.