Integrating Camera Access in Capacitor Apps: A Practical Guide for iOS and Android
A practical guide to adding cross-platform camera access in Capacitor apps using the @capacitor/camera plugin, covering permission setup, capture options, error handling, and platform-specific gotchas.
22 Jul 2026, 03:12 UTC

The Problem: Cross-Platform Camera Access Without Native Code
Mobile apps frequently need camera access—for profile photos, document scanning, or AR features. Writing separate Swift and Kotlin implementations is time-consuming and introduces platform-specific bugs. Capacitor's Camera plugin solves this by exposing a single JavaScript API that works on both iOS and Android, handling permission requests, image capture, and file output through a unified interface.
This guide walks through adding camera functionality to a Capacitor project, covering permission configuration, the capture workflow, and handling common failure modes.
Prerequisites
- A Capacitor project (v4 or later) with iOS and Android platforms added
- Node.js 18+ and npm installed
- Physical test devices for both platforms (simulators/emulators lack camera hardware)
- Xcode 15+ for iOS builds, Android Studio with SDK 34+ for Android
Install the Plugin and Sync Native Code
Run the following in your project root (where package.json lives):
npm install @capacitor/camera
npx cap sync
The cap sync command copies the plugin's native Swift and Kotlin code into your ios/App/App and android/app directories. You need write permissions to those folders. If cap sync fails, verify your platforms were added with npx cap add ios and npx cap add android.
Configure Platform Permissions
Camera access requires explicit user consent on both platforms. You must declare the permission in each native project before the plugin can prompt the user.
iOS: Info.plist
Open ios/App/App/Info.plist in Xcode (or any text editor) and add:
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to capture profile photos and documents.</string>
The string appears in the system permission dialog. Without this key, iOS will crash the app when the plugin attempts camera access.
Android: AndroidManifest.xml
Open android/app/src/main/AndroidManifest.xml and add the CAMERA permission inside the <manifest> element, before <application>:
<uses-permission android:name="android.permission.CAMERA" />
For Android 12 (API 31) and higher, the plugin handles runtime permission requests automatically. No additional manifest changes are required for foreground camera use.
Capture an Image: Basic Implementation
Import the plugin and call getPicture() with options. This example captures a JPEG at 80% quality, resized to a maximum of 1280px on the longest edge:
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
async function capturePhoto() {
try {
const image = await Camera.getPicture({
quality: 80,
allowEditing: false,
resultType: CameraResultType.Base64, // or 'Uri' for file path
source: CameraSource.Camera,
width: 1280,
height: 1280,
preserveAspectRatio: true,
correctOrientation: true,
presentationStyle: 'fullscreen', // iOS only
saveToGallery: false
});
// image.base64String contains the JPEG data
const imageUrl = `data:image/jpeg;base64,${image.base64String}`;
document.getElementById('preview').src = imageUrl;
} catch (err) {
if (err.code === 'NOT_AVAILABLE') {
// No camera hardware or permission denied
console.warn('Camera unavailable:', err.message);
} else if (err.code === 'CANCELLED') {
// User dismissed the camera UI
console.log('User cancelled capture');
} else {
console.error('Capture failed:', err);
}
}
}
Key Options Explained
| Option | Type | Purpose |
|---|---|---|
quality | number (0-100) | JPEG compression level. 80 balances file size and visual quality. |
resultType | CameraResultType | Base64 returns a data URI string; Uri returns a capacitor:// file path; DataUrl returns a full data URL. |
source | CameraSource | Camera launches the camera; Photos opens the gallery picker; Prompt shows a native action sheet. |
width/height | number | Maximum dimensions. The plugin resizes maintaining aspect ratio when preserveAspectRatio is true. |
correctOrientation | boolean | Rotates the image based on EXIF orientation. Essential for correct display on iOS. |
Handle Permission Denial Gracefully
Users can deny camera permission. The plugin throws an error with code: 'NOT_AVAILABLE' in this case. Your UI should detect this and offer a fallback—such as a gallery picker or a button that opens system settings.
async function captureWithFallback() {
try {
return await Camera.getPicture({ source: CameraSource.Camera, resultType: CameraResultType.Uri });
} catch (err) {
if (err.code === 'NOT_AVAILABLE') {
// Offer gallery as alternative
const fromGallery = await Camera.getPicture({ source: CameraSource.Photos, resultType: CameraResultType.Uri });
return fromGallery;
}
throw err;
}
}
On iOS, you can deep-link to the app's settings page using UIApplication.openSettingsURLString via a custom plugin or Capacitor's App API. On Android, use an intent to Settings.ACTION_APPLICATION_DETAILS_SETTINGS.
Android 12+ Background Camera Restrictions
Starting with Android 12 (API 31), apps cannot access the camera while in the background. The plugin enforces foreground-only usage. Ensure camera capture is triggered by explicit user action (button tap, not a background timer or push notification handler). If your app needs background scanning, you must implement a foreground service with the FOREGROUND_SERVICE_CAMERA permission—a separate implementation beyond this plugin's scope.
Expected Checks After Integration
- Permission prompt appears on first camera launch on both platforms.
- Image captures without crash and the returned object contains
base64String(ifBase64) or a validcapacitor://URI. - Orientation is correct on iOS when
correctOrientation: true. - Resizing works: verify the output dimensions match your
width/heightconstraints. - Cancellation handling: dismissing the camera UI returns a
CANCELLEDerror, not a crash.
Common Failure Modes and Recovery
| Symptom | Likely Cause | Recovery |
|---|---|---|
| App crashes on iOS launch | Missing NSCameraUsageDescription in Info.plist | Add the key with a descriptive string; rebuild. |
| Permission denied error immediately | User previously denied; OS blocks re-prompt | Guide user to Settings → App → Camera toggle. |
| Black preview on Android | Another app holds camera lock | Close other camera apps; ensure onPause releases camera (plugin handles this). |
| Out-of-memory on large images | High-res capture without resizing | Set width/height to downsample before Base64 encoding. |
| Base64 string too large for JSON payload | Uncompressed or high-res image | Use resultType: 'Uri' and upload the file via multipart/form-data instead. |
Limitations to Consider
- No video capture: The plugin supports
mediaType: 'video'in options, but video recording behavior varies significantly between platforms. Test thoroughly if video is required. - No manual controls: Exposure, focus, white balance, and zoom are not exposed. For advanced camera features, you need a custom native plugin.
- Base64 memory overhead: Large images encoded as Base64 strings can exceed JavaScript memory limits on older devices. Prefer
resultType: 'Uri'for uploads. - Simulator/emulator limitations: Camera hardware is not available in iOS Simulator or most Android emulators. Physical device testing is mandatory.
Verification Checklist
Before shipping, confirm each item on a physical device per platform:
- Fresh install → first camera tap shows system permission dialog
- Permission granted → capture returns valid image data
- Permission denied → fallback UI appears, no crash
- Cancel button →
CANCELLEDerror caught, UI remains responsive - Rotate device during capture → output orientation correct (iOS)
- Capture multiple images in succession → no memory leaks or crashes
Next Steps
With basic capture working, consider:
- Uploading the
Uriresult viafetchwithFormDatato avoid Base64 bloat - Adding EXIF stripping for privacy (use a library like
exifron the Base64 data) - Implementing a custom camera overlay for guided capture (requires native plugin)
The Capacitor Camera plugin handles the common case well. For anything beyond point-and-shoot, plan for native development.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.