VOOZH about

URL: https://capacitorjs.com/docs/apis/camera

⇱ Camera Capacitor Plugin API | Capacitor Documentation


Skip to main content
An OutSystems Company →
Version: v8

The Camera API provides the ability to take a photo with the camera or choose an existing one from the photo album.

Install

npminstall @capacitor/camera
npx cap sync

iOS

iOS requires the following usage description be added and filled out for your app in Info.plist:

  • NSCameraUsageDescription (Privacy - Camera Usage Description)
  • NSPhotoLibraryAddUsageDescription (Privacy - Photo Library Additions Usage Description)
  • NSPhotoLibraryUsageDescription (Privacy - Photo Library Usage Description)

Read about Configuring Info.plist in the iOS Guide for more information on setting iOS permissions in Xcode

Android

When picking existing images from the device gallery, the Android Photo Picker component is now used. The Photo Picker is available on devices that meet the following criteria:

  • Run Android 11 (API level 30) or higher
  • Receive changes to Modular System Components through Google System Updates

Older devices and Android Go devices running Android 11 or 12 that support Google Play services can install a backported version of the photo picker. To enable the automatic installation of the backported photo picker module through Google Play services, add the following entry to the <application> tag in your AndroidManifest.xml file:

<!-- Trigger Google Play services to install the backported photo picker module. -->
<!--suppress AndroidDomInspection -->
<serviceandroid:name="com.google.android.gms.metadata.ModuleDependencies"
android:enabled="false"
android:exported="false"
tools:ignore="MissingClass">
<intent-filter>
<actionandroid:name="com.google.android.gms.metadata.MODULE_DEPENDENCIES"/>
</intent-filter>
<meta-dataandroid:name="photopicker_activity:0:required"android:value=""/>
</service>

If that entry is not added, on devices that don't support the Photo Picker, the Photo Picker component falls back to Intent.ACTION_OPEN_DOCUMENT.

The Camera plugin requires no permissions, unless using saveToGallery: true, in that case the following permissions should be added to your AndroidManifest.xml:

<uses-permissionandroid:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

You can also specify those permissions only for the Android versions where they will be requested:

<uses-permissionandroid:name="android.permission.READ_EXTERNAL_STORAGE"android:maxSdkVersion="32"/>
<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"android:maxSdkVersion="29"/>

The storage permissions are for reading/saving photo files.

Read about Setting Permissions in the Android Guide for more information on setting Android permissions.

Additionally, because the Camera API launches a separate Activity to handle taking the photo, you should listen for appRestoredResult in the App plugin to handle any camera data that was sent in the case your app was terminated by the operating system while the Activity was running.

Variables

This plugin will use the following project variables (defined in your app's variables.gradle file):

  • androidxExifInterfaceVersion: version of androidx.exifinterface:exifinterface (default: 1.4.1)
  • androidxMaterialVersion: version of com.google.android.material:material (default: 1.13.0)

PWA Notes

On Web, takePhoto can use the PWA Elements pwa-camera-modal custom element to provide a native-like camera UI. If the element is not registered, the plugin falls back to an <input type="file"> picker. chooseFromGallery always uses <input type="file"> on Web, regardless of whether PWA Elements are installed.

Installing PWA Elements programmatically

See the PWA Elements installation guide for full instructions.

Providing a custom camera element

Instead of using @ionic/pwa-elements, you can register your own pwa-camera-modal custom element. The plugin interacts with it using the following interface:

MemberTypeDescription
facingModestring propertySet to 'user' (front camera) or 'environment' (rear camera) before presenting
componentOnReady()method → Promise<void>Called by the plugin after creating the element; resolve when the element is ready
present()methodCalled by the plugin to display the camera UI
dismiss()methodCalled by the plugin to close the camera UI after a photo is taken or cancelled
onPhotoeventDispatched when the user takes a photo or cancels. event.detail must be a Blob (photo taken), null (user cancelled), or an Error (something went wrong)
classMyCameraModalextendsHTMLElement{
facingMode ='environment';

componentOnReady(){
returnPromise.resolve();
}

present(){
// Show your custom camera UI, then dispatch exactly one 'onPhoto' event when done:
// - Blob: user took a photo
// - null: user cancelled
// - Error: something went wrong
// Example:
this.dispatchEvent(newCustomEvent('onPhoto',{ detail: photoBlob }));
}

dismiss(){
// Hide your custom camera UI (called by the plugin after receiving 'onPhoto')
}
}

customElements.define('pwa-camera-modal', MyCameraModal);

Examples

Taking a photo

import{ Camera }from'@capacitor/camera';

consttakePicture=async()=>{
try{
const result =await Camera.takePhoto({
quality:90,
includeMetadata:true,
});

// result.webPath can be set directly as the src of an image element
imageElement.src = result.webPath;

// On native: pass result.uri to the Filesystem API to get the full-resolution base64,
// or use result.thumbnail for a lower-resolution base64 preview.
// On Web: result.thumbnail contains the full image base64 encoded.

console.log('Format:', result.metadata?.format);
console.log('Resolution:', result.metadata?.resolution);
}catch(e){
const error = e asany;
// error.code contains the structured error code (e.g. 'OS-PLUG-CAMR-0003')
// when thrown by the native layer. See the Errors section for all codes.
const message = error.code ?`[${error.code}] ${error.message}`: error.message;
console.error('takePhoto failed:', message);
}
};
import{ Camera, MediaTypeSelection }from'@capacitor/camera';

constpickMedia=async()=>{
try{
const{ results }=await Camera.chooseFromGallery({
mediaType: MediaTypeSelection.All,// photos, videos, or both
allowMultipleSelection:true,
limit:5,
includeMetadata:true,
});

for(const item of results){
console.log('Type:', item.type);// MediaType.Photo or MediaType.Video
console.log('webPath:', item.webPath);
console.log('Format:', item.metadata?.format);
console.log('Size:', item.metadata?.size);
}
}catch(e){
const error = e asany;
const message = error.code ?`[${error.code}] ${error.message}`: error.message;
console.error('chooseFromGallery failed:', message);
}
};

Recording and playing a video

import{ Camera }from'@capacitor/camera';

constrecordAndPlay=async()=>{
let videoUri:string|undefined;

try{
const result =await Camera.recordVideo({
saveToGallery:false,
isPersistent:true,// keep the file available across app launches
includeMetadata:true,
});

videoUri = result.uri;
console.log('Duration:', result.metadata?.duration);
console.log('Saved to gallery:', result.saved);
}catch(e){
const error = e asany;
const message = error.code ?`[${error.code}] ${error.message}`: error.message;
console.error('recordVideo failed:', message);
return;
}

if(videoUri){
try{
await Camera.playVideo({ uri: videoUri });
}catch(e){
const error = e asany;
const message = error.code ?`[${error.code}] ${error.message}`: error.message;
console.error('playVideo failed:', message);
}
}
};

Editing a photo from a base64 string

editPhoto opens an in-app editor from a base64-encoded image and returns the edited image as a base64 string in outputImage.

import{ Camera }from'@capacitor/camera';

consteditFromBase64=async(base64Image:string)=>{
try{
const{ outputImage }=await Camera.editPhoto({
inputImage: base64Image,// raw base64, no data URL prefix
});

// outputImage is the edited image, base64 encoded
imageElement.src =`data:image/jpeg;base64,${outputImage}`;
}catch(e){
const error = e asany;
const message = error.code ?`[${error.code}] ${error.message}`: error.message;
console.error('editPhoto failed:', message);
}
};

Editing a photo from a URI

editURIPhoto opens an in-app editor from a file URI (e.g. from takePhoto or the Filesystem API) and returns a MediaResult.

import{ Camera }from'@capacitor/camera';

consteditFromURI=async(uri:string)=>{
try{
const result =await Camera.editURIPhoto({
uri,
saveToGallery:false,
includeMetadata:true,
});

// result.webPath can be used directly as an image src
imageElement.src = result.webPath;

console.log('Format:', result.metadata?.format);
console.log('Size:', result.metadata?.size);
console.log('Saved to gallery:', result.saved);
}catch(e){
const error = e asany;
const message = error.code ?`[${error.code}] ${error.message}`: error.message;
console.error('editURIPhoto failed:', message);
}
};

Migrating to the New API

Version 8.1.0 introduces a new improved API and deprecates getPhoto and pickImages.

Replacing getPhoto

getPhoto handled three sources via CameraSource: Camera, Photos, and Prompt. Camera and Photos now map to different methods, while Prompt was removed.

CameraSource.Camera to takePhoto

CameraResultType.Base64 and CameraResultType.DataUrl are not supported in the new API. See Result type changes for alternatives.

// Before
const photo =await Camera.getPhoto({
source: CameraSource.Camera,
quality:90,
allowEditing:true,
resultType: CameraResultType.Uri,
direction: CameraDirection.Rear,
width:1280,
height:720,
});
const imageUrl = photo.webPath;

// After
const result =await Camera.takePhoto({
quality:90,
editable:'in-app',// replaces allowEditing: true
cameraDirection: CameraDirection.Rear,// replaces direction
targetWidth:1280,// replaces width (1)
targetHeight:720,// replaces height (1)
});
const imageUrl = result.webPath;

(1) width/height each worked independently and set a maximum dimension while preserving aspect ratio. targetWidth/targetHeight must be used together — setting only one has no effect.

CameraSource.Photos to chooseFromGallery

// Before
const photo =await Camera.getPhoto({
source: CameraSource.Photos,
quality:90,
resultType: CameraResultType.Uri,
});
const imageUrl = photo.webPath;

// After
const{ results }=await Camera.chooseFromGallery({
quality:90,
});
const imageUrl = results[0].webPath;

CameraSource.Prompt (or default)

getPhoto previously displayed a native prompt letting the user choose between the camera and the gallery. This prompt is no longer part of the plugin. You should build the prompt using your own UI (for example, with @capacitor/action-sheet) and then call takePhoto or chooseFromGallery based on the user's selection.

// Before
const photo =await Camera.getPhoto({
// source defaults to CameraSource.Prompt
quality:90,
resultType: CameraResultType.Uri,
});

// After: show your own UI to determine the source, then call the appropriate method
const result =await Camera.takePhoto({ quality:90});
// or
const{ results }=await Camera.chooseFromGallery({ quality:90});

Result type changes

getPhoto returned a Photo object where the fields available depended on resultType. The new API removes resultType entirely — MediaResult has a fixed set of fields regardless of how the photo was taken.

Photo fieldMediaResult equivalent
pathuri
webPathwebPath
base64Stringthumbnail (on Web, contains the full image base64 encoded; on native, contains a thumbnail)
dataUrlNo direct equivalent — see note below
savedsaved
formatmetadata.format (requires includeMetadata: true)
exifmetadata.exif (requires includeMetadata: true)

Constructing a data URL — two options are available depending on your needs:

On all platforms, you can combine thumbnail and metadata.format (requires includeMetadata: true). On native, thumbnail is lower-resolution:

const dataUrl =`data:image/${result.metadata.format};base64,${result.thumbnail}`;

On native, if you need the full-resolution base64, read uri via the Filesystem API and construct the data URL from there:

import{ Filesystem }from'@capacitor/filesystem';

const{ data }=await Filesystem.readFile({ path: result.uri });
const dataUrl =`data:image/${result.metadata.format};base64,${data}`;

Replacing pickImageschooseFromGallery

pickImages allowed selecting multiple photos from the gallery. Pass allowMultipleSelection: true to chooseFromGallery to get the same behaviour.

// Before
const{ photos }=await Camera.pickImages({
quality:90,
limit:5,
width:1280,
height:720,
});
for(const photo of photos){
console.log(photo.webPath);
}

// After
const{ results }=await Camera.chooseFromGallery({
allowMultipleSelection:true,
quality:90,
limit:5,
targetWidth:1280,// replaces width (1)
targetHeight:720,// replaces height (1)
});
for(const result of results){
console.log(result.webPath);
}

(1) width/height each worked independently and set a maximum dimension while preserving aspect ratio. targetWidth/targetHeight must be used together — setting only one has no effect.

chooseFromGallery can also select videos or mixed media by setting mediaType to MediaTypeSelection.Video or MediaTypeSelection.All.

Option rename summary

Old optionNew optionApplies to
widthtargetWidth (1)takePhoto, chooseFromGallery
heighttargetHeight (1)takePhoto, chooseFromGallery
directioncameraDirectiontakePhoto
allowEditingeditable: 'in-app'takePhoto, chooseFromGallery
resultType— (removed, see Result type changes)
source— (removed, use separate methods)
promptLabel*— (removed, build your own UI)

(1) width/height each worked independently and set a maximum dimension while preserving aspect ratio. targetWidth/targetHeight must be used together — setting only one has no effect.

API

For a list of existing error codes, see Errors.

Errors

The plugin returns structured errors on Android and iOS. Each error has a code (e.g. OS-PLUG-CAMR-0003) and a message with a human-readable description. Note that these are only available for native platforms starting on the new APIs introduced in version 8.1.0: takePhoto, chooseFromGallery, editPhoto, editURIPhoto, recordVideo, and playVideo.

Error codePlatform(s)Description
OS-PLUG-CAMR-0003Android, iOSCouldn't access camera. Check your camera permissions and try again.
OS-PLUG-CAMR-0005Android, iOSCouldn't access your photo gallery because access wasn't provided.
OS-PLUG-CAMR-0006Android, iOSCouldn't take photo because the process was canceled.
OS-PLUG-CAMR-0007Android, iOSNo camera available.
OS-PLUG-CAMR-0008iOSThe selected file contains data that isn't valid.
OS-PLUG-CAMR-0009Android, iOSCouldn't edit image.
OS-PLUG-CAMR-0010Android, iOSCouldn't take photo.
OS-PLUG-CAMR-0011iOSCouldn't get image from the gallery.
OS-PLUG-CAMR-0012Android, iOSCouldn't process image.
OS-PLUG-CAMR-0013Android, iOSCouldn't edit photo because the process was canceled.
OS-PLUG-CAMR-0014iOSCouldn't decode the 'Take Photo' action parameters.
OS-PLUG-CAMR-0016Android, iOSCouldn't record video.
OS-PLUG-CAMR-0017Android, iOSCouldn't record video because the process was canceled.
OS-PLUG-CAMR-0018Android, iOSCouldn't choose media from the gallery.
OS-PLUG-CAMR-0019iOSCouldn't encode the media result.
OS-PLUG-CAMR-0020Android, iOSCouldn't choose media from the gallery because the process was canceled.
OS-PLUG-CAMR-0021AndroidCouldn't get media file path.
OS-PLUG-CAMR-0023Android, iOSCouldn't play video.
OS-PLUG-CAMR-0024AndroidURI parameter cannot be empty.
OS-PLUG-CAMR-0025iOSCouldn't get video from the gallery.
OS-PLUG-CAMR-0026iOSThere's an issue with the plugin.
OS-PLUG-CAMR-0027Android, iOSThe selected file doesn't exist.
OS-PLUG-CAMR-0028Android, iOSCouldn't retrieve image from the URI.
OS-PLUG-CAMR-0031AndroidInvalid argument provided to plugin method.
OS-PLUG-CAMR-0033AndroidUnable to get the context.