---
title: How to Take and Edit Photos in a Capacitor App
description: Take and edit photos in a Capacitor app — permissions, canceled pickers, HEIC conversion, upload, and which plugin handles each step.
date:
  created: 2026-09-26
  updated: 2026-09-26
authors:
  - djabif
categories:
  - Capacitor
  - Guides
links:
  - Capacitor File Opener: sdks/capacitor/file-opener.md
  - Capacitor Photo Manipulator: sdks/capacitor/photo-manipulator.md
  - Capacitor Exif: sdks/capacitor/exif.md
  - Capacitor Photo Editor: sdks/capacitor/photo-editor.md
  - Capacitor File Compressor: sdks/capacitor/file-compressor.md
  - Capacitor File Transfer: sdks/capacitor/file-transfer.md
faq: true
---

# How to Take and Edit Photos in a Capacitor App

Photographing a receipt for an expense app, cropping a user's profile picture, and letting someone touch up a photo before it's posted each need a different plugin, because what happens after the photo is taken decides which tool fits. This guide walks through those scenarios in order for taking and editing photos in a Capacitor app: capturing the photo, showing it full-screen, resizing or converting it, handing it to an editor app on Android, shrinking it, and uploading it.

<!-- more -->

<div class="capawesome-z29o10a">
  <a href="https://capawesome.io/" target="_blank">
    <img alt="Thousands of teams ship faster with Capawesome Cloud Native Builds and Live Updates" src="https://capawesome.io/assets/banners/cloud-teams-ship-faster-with-capacitor.png" />
  </a>
</div>

**Key takeaways:**

- `@capacitor/camera` (official) captures or picks a photo and returns a URI or base64 string; it's a single snapshot, not a live feed.
- The [Capacitor Photo Manipulator plugin](../../sdks/capacitor/photo-manipulator.md) does headless crop, resize, rotate, flip, and format conversion, including HEIC/AVIF to JPEG, PNG, or WebP, on Android, iOS, and web.
- The [Capacitor Photo Editor plugin](../../sdks/capacitor/photo-editor.md) is Android-only and hands the photo to an installed editing app like Google Photos; it doesn't embed an editor.
- The [Capacitor File Compressor plugin](../../sdks/capacitor/file-compressor.md), a [Capawesome Insiders](https://capawesome.io/insiders/){:target="_blank"} plugin, shrinks the final PNG, JPEG, or WebP file, a separate job from transforming it with Photo Manipulator.
- The [Capacitor Exif plugin](../../sdks/capacitor/exif.md) reads, writes, and removes EXIF and GPS metadata without re-encoding the image, unlike Photo Manipulator's stripping, which is a side effect of transforming it.
- The [Capacitor File Opener plugin](../../sdks/capacitor/file-opener.md) opens a photo in the platform's own viewer (an image app on Android, the system preview on iOS) in one call.

## Capture a photo

The official [`@capacitor/camera`](https://capacitorjs.com/docs/apis/camera){:target="_blank"} plugin captures a photo from the device camera or lets the user pick one from their gallery. `Camera.getPhoto()` takes a `CameraResultType` (`Uri`, `Base64`, or `DataUrl`) and a `CameraSource` (`Camera`, `Photos`, or `Prompt`, which shows both options), and resolves once the user has taken or selected a photo. On web, it falls back to an `<input type="file">` picker unless PWA Elements' `pwa-camera-modal` is registered for a native-like camera UI, so the same call works across all three platforms without a separate code path:

```typescript
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';

try {
  const photo = await Camera.getPhoto({
    quality: 90,
    resultType: CameraResultType.Uri,
    source: CameraSource.Prompt,
  });
} catch (error) {
  // User backed out of the camera or picker; not a real failure.
}
```

That `try`/`catch` isn't optional. If the user cancels, `getPhoto()` rejects with error code `OS-PLUG-CAMR-0006` and the message "Couldn't take photo because the process was canceled." Skip the `catch` and that becomes an unhandled rejection the first time someone backs out instead of taking a photo, which is often.

`photo.webPath` (or `photo.base64String`, depending on `resultType`) is the input every other section in this guide works from. For more than one photo at a time, [`pickImages()`](https://capacitorjs.com/docs/apis/camera#pickimages){:target="_blank"} picks multiple from the gallery in one call and returns a `GalleryPhotos` array (cap it with `limit`, honored on Android 13+ and iOS); everything past this point applies to each photo in it the same way.

The plugin also covers the lightest edits itself, on Android and iOS only. `allowEditing: true` opens the OS crop UI right after capture, on iOS only for `CameraSource.Camera`, not for photos picked from the library. `width` and `height` cap the saved image's dimensions while keeping the aspect ratio, and `correctOrientation` (default `true`) rotates the result upright. If a capped size and an optional user crop are all the editing your app needs, you're done here; the sections below are for everything those options don't reach.

### Permissions

The Camera plugin's permissions depend on which capability you use, not on adding the plugin alone. On iOS, add `Info.plist` keys for each one: `NSCameraUsageDescription` for capturing a photo, `NSPhotoLibraryUsageDescription` for picking one from the gallery, and `NSPhotoLibraryAddUsageDescription` only if you set `saveToGallery: true`.

```xml title="ios/App/App/Info.plist"
<key>NSCameraUsageDescription</key>
<string>This app uses the camera to let you take photos.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to your photo library to let you pick photos.</string>
```

On Android, plain capture with `getPhoto()` needs no manifest permissions at all; Android only asks for `READ_EXTERNAL_STORAGE` and `WRITE_EXTERNAL_STORAGE`, and only if `saveToGallery: true` is set. Both are capped with `maxSdkVersion` on purpose: scoped storage on Android 10+ (API 29) already lets you write to the gallery through `MediaStore` without `WRITE_EXTERNAL_STORAGE`, and Android 13 (API 33) replaced `READ_EXTERNAL_STORAGE` with granular media permissions like `READ_MEDIA_IMAGES`, which the plugin's own docs don't cover yet; check [Android's permission docs](https://developer.android.com/about/versions/13/behavior-changes-13#granular-media-permissions){:target="_blank"} if your app targets API 33 or newer.

```xml title="android/app/src/main/AndroidManifest.xml"
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
```

Neither Photo Manipulator nor Photo Editor needs its own runtime permissions beyond this; they operate on a URI you already have access to.

### Live camera preview

`Camera.getPhoto()` doesn't give you a live camera feed. It opens the platform's camera UI, waits for the user to take or pick a photo, and resolves once that UI closes; there's no frame-by-frame access while it's open. A live preview, the kind a custom capture screen or continuous barcode-style scanning needs (our [embedded barcode scanner](./capacitor-embedded-barcode-scanner.md) is built exactly this way), is a native camera view layered under or over the WebView, not a plugin call. If the use case is "let the user take one photo," `getPhoto()` is enough; if it's "show the camera feed inside my own UI," that's a dedicated native view, outside what `@capacitor/camera` does.

## View a photo

There are two ways to show a captured photo, and they serve different needs. Inside your own UI, an `<img>` element pointed at `photo.webPath` is enough. To give the user the system's own viewer, with zoom and a share sheet built in, the [Capacitor File Opener plugin](../../sdks/capacitor/file-opener.md) hands the file to the platform in one call.

### In your own UI

Embedding the photo in the WebView doesn't need a plugin. `photo.webPath` is already a URL the WebView can load, so a full-screen container with an `<img>` inside covers a preview or a tap-to-enlarge modal:

```html
<div class="photo-viewer">
  <img id="photo" alt="Captured photo" />
</div>
```

Set the `src` from the capture result:

```typescript
document.querySelector<HTMLImageElement>('#photo')!.src = photo.webPath!;
```

Then style the container to fill the screen over a black backdrop:

```css
.photo-viewer {
  position: fixed;
  inset: 0;
  background: black;
  display: flex;
  align-items: center;
  justify-content: center;
}
.photo-viewer img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
```

The same `<img>` pattern works for thumbnails in a list; point it at a smaller version of the file (see [Transform and optimize it](#transform-and-optimize-it) for how to produce one). Pinch-to-zoom and swipe-between-photos galleries are a web-layer UI concern (gesture handling, CSS transforms), not something a plugin provides; if you want those without building them, the next option hands the photo to the platform's viewer, which already has them.

### In the default viewer

The [Capacitor File Opener plugin](../../sdks/capacitor/file-opener.md) (`@capawesome-team/capacitor-file-opener`, free, Android, iOS, and web) opens the photo with the platform's own file handling. On Android that's whichever app the user has associated with images, for example Google Photos; on iOS it's the system preview (`UIDocumentInteractionController`), which shows the photo full-screen with pinch-to-zoom and a share button, without leaving your app. Call [`openFile(...)`](../../sdks/capacitor/file-opener.md#openfile) with the native path on Android and iOS or a `blob` on web; the MIME type is detected automatically:

```typescript
import { FileOpener } from '@capawesome-team/capacitor-file-opener';

await FileOpener.openFile({ path: photo.path! });
```

The trade-off is control. What the user can do with the photo from there (save, share, edit) is decided by the platform viewer. On Android, the directory holding the photo has to be declared in `file_paths.xml`, the same FileProvider setup Photo Editor needs (see [Editor hand-off](#editor-hand-off)). Reach for the `<img>` approach when the photo is part of your own screen, and for File Opener when the user wants to inspect it the way they would any photo on their device.

## Transform and optimize it

Cropping, resizing, and converting a photo is Photo Manipulator's job; shrinking its file size is File Compressor's; reading or writing its metadata is Exif's. All three are headless and run on the file path the Camera plugin gave you.

### Crop, resize, and convert

The [Capacitor Photo Manipulator plugin](../../sdks/capacitor/photo-manipulator.md) (`@capawesome/capacitor-photo-manipulator`) does headless image transforms on Android, iOS, and web: crop, resize, rotate, flip, and format conversion, with no UI. Passing several at once applies them in a fixed order, crop, then resize, then rotate, then flip; chain two calls if you need a different one. Turning an iPhone's HEIC into a JPEG is one [`transform(...)`](../../sdks/capacitor/photo-manipulator.md#transform) call:

```typescript
import { PhotoManipulator, ImageFormat } from '@capawesome/capacitor-photo-manipulator';

const { path } = await PhotoManipulator.transform({
  path: photo.path!,
  format: ImageFormat.Jpeg,
  quality: 0.9,
});
```

The plugin is built around three problems that come up with photos from real devices:

- **HEIC and AVIF conversion.** Photos taken on an iPhone are HEIC by default, and most servers and browsers can't display that format directly. Photo Manipulator converts HEIC or AVIF to JPEG, PNG, or WebP using the platform's own decoders, not a WASM library, which avoids the memory spikes that come with decoding large images in the WebView. One limit: iOS can't write WebP, so `ImageFormat.Webp` rejects there with `UNSUPPORTED_FORMAT`.
- **EXIF orientation.** The plugin applies the EXIF orientation during decoding, so output is always upright, instead of sideways photos that only look correct in apps that happen to read the orientation tag.
- **Metadata stripping.** All metadata, including EXIF and GPS coordinates, is stripped from the output by re-encoding, so a photo that leaves the app doesn't carry the user's location with it.

When you pass a `resize` target, the plugin decodes the image downsampled, so a thumbnail from a 12-megapixel photo never loads the full-resolution bitmap; results are written to files rather than returned as base64, which keeps the WebView's memory out of the equation.

### Read or write metadata

The [Capacitor Exif plugin](../../sdks/capacitor/exif.md) (`@capawesome/capacitor-exif`, Android and iOS) reads, writes, and removes EXIF tags directly, without touching pixel data, including on HEIC files. Photo Manipulator's stripping happens whether you want it or not; this plugin is for the deliberate cases: reading GPS coordinates or camera details, writing a corrected capture date, or removing only the GPS tag while leaving the rest. [`readExif(...)`](../../sdks/capacitor/exif.md#readexif) returns the tags typed:

```typescript
import { Exif } from '@capawesome/capacitor-exif';

const { tags } = await Exif.readExif({ path: photo.path! });
console.log(tags.gpsLatitude, tags.gpsLongitude, tags.dateTimeOriginal);
```

[`writeExif(...)`](../../sdks/capacitor/exif.md#writeexif) updates only the tags you pass, and [`removeExif(...)`](../../sdks/capacitor/exif.md#removeexif) strips everything but keeps the orientation tag by default so the photo doesn't come back rotated. Reach for this plugin instead of Photo Manipulator when metadata is the only thing that needs to change.

### Shrink the file

Once a photo is the right dimensions and format, the [Capacitor File Compressor plugin](../../sdks/capacitor/file-compressor.md) (`@capawesome-team/capacitor-file-compressor`, a Capawesome Insiders plugin) compresses PNG, JPEG, and WebP images to reduce the file size before upload or storage. [`compressImage(...)`](../../sdks/capacitor/file-compressor.md#compressimage) takes a `quality` between `0` and `1` (default `0.6`), optional `width` and `height` if you want to shrink the dimensions in the same step, and a `mimeType` for the output, where iOS only writes `image/jpeg` while Android and web also take `image/webp`:

```typescript
import { FileCompressor } from '@capawesome-team/capacitor-file-compressor';

const { path } = await FileCompressor.compressImage({
  path: photo.path!,
  mimeType: 'image/jpeg',
  quality: 0.7,
});
```

It's a separate plugin from Photo Manipulator on purpose: Photo Manipulator changes what the image *is* (its dimensions, orientation, format), File Compressor changes how much space it takes up. Compression re-encodes the image, so EXIF data is lost here too; read anything you need with the Exif plugin before this step.

## Editor hand-off

If you want the user to touch up a photo themselves rather than applying a fixed transform, the [Capacitor Photo Editor plugin](../../sdks/capacitor/photo-editor.md) (`@capawesome/capacitor-photo-editor`) hands the photo over to an installed editing app on the device, such as Google Photos. [`editPhoto(...)`](../../sdks/capacitor/photo-editor.md#editphoto) takes the file path and resolves when the user leaves the editor:

```typescript
import { PhotoEditor } from '@capawesome/capacitor-photo-editor';

await PhotoEditor.editPhoto({ path: photo.path! });
```

The method returns nothing. The edit lands in the original file, which means the user has to overwrite the image when saving in the editing app; if they save a copy instead, your app still holds the untouched original and has no path to the new file. Plan the UX around that, for example by telling the user to save in place, or by copying the photo first and handing the copy to the editor.

This is Android-only. There's no iOS equivalent in this plugin, and it doesn't embed an editing interface inside your app; it opens whatever photo editor the user already has installed and waits for them to finish. That makes it a fit for profile picture touch-ups or marking up a screenshot for a bug report on Android. If you need the same capability on iOS, or an editor embedded in your own UI, Photo Editor isn't that; you'd be looking at a custom native implementation.

Android setup needs a `file_paths.xml` FileProvider configuration; the [Capacitor Photo Editor plugin](../../sdks/capacitor/photo-editor.md) documentation has the exact file.

## Upload the photo

By the time `getPhoto()` resolves, the photo is already written to disk, so storing it is done; what's left is where it goes next. One caveat before it goes anywhere: that file lives in the app's cache, and so does Photo Manipulator's output, which its README says is deleted on the next app launch. If a photo has to survive locally past the current session, copy it to `Directory.Data` with [`@capacitor/filesystem`](https://capacitorjs.com/docs/apis/filesystem){:target="_blank"} first. If it's only a stepping stone to an upload, leave it where it is.

Where it goes depends on your backend: Firebase has a dedicated plugin, and any other API takes either a plain `fetch` or, for photos big enough to outlast the user's patience, a background upload.

### To Firebase Storage

The [Capacitor Firebase Storage plugin](../../sdks/capacitor/firebase/cloud-storage.md) uploads the file from its native `uri` (or a `blob` on web) with [`uploadFile(...)`](../../sdks/capacitor/firebase/cloud-storage.md#uploadfile) and reports progress through a callback; `bytesTransferred` and `totalBytes` are only filled in on Android and web:

```typescript
import { FirebaseStorage } from '@capacitor-firebase/storage';

await FirebaseStorage.uploadFile(
  { path: `photos/${Date.now()}.jpg`, uri: photo.path! },
  (event, error) => {
    if (event) {
      console.log(`Progress: ${event.progress * 100}%`);
    }
  },
);
```

The [Firebase Cloud Storage guide](./capacitor-firebase-cloud-storage-guide.md) covers the rest of that plugin.

### To your own API

A plain REST upload needs no plugin at all. `photo.webPath` is a URL the WebView can `fetch`, so read it as a `blob`, put it in a `FormData`, and `POST` it:

```typescript
const blob = await (await fetch(photo.webPath!)).blob();
const formData = new FormData();
formData.append('file', blob, 'photo.jpg');
await fetch('https://example.tld/photos', { method: 'POST', body: formData });
```

That's the pattern the [profile picture example](#profile-picture-example) below uses end to end, and [Capacitor File Handling: The Complete Guide](./capacitor-file-handling-guide.md) covers its edge cases. Its limit is that the upload lives in the WebView: the whole file passes through JavaScript memory, and the request dies if the user switches apps mid-upload.

For a full-resolution photo on a slow connection, the [Capacitor File Transfer plugin](../../sdks/capacitor/file-transfer.md) (`@capawesome-team/capacitor-file-transfer`, a Capawesome Insiders plugin, Android and iOS) uploads straight from the native path in the background instead. [`startUpload(...)`](../../sdks/capacitor/file-transfer.md#startupload) resolves immediately with a transfer ID, sends the file as `multipart/form-data` by default, and keeps going while the app is backgrounded; progress and completion arrive as events:

```typescript
import { FileTransfer } from '@capawesome-team/capacitor-file-transfer';

await FileTransfer.addListener('transferCompleted', event => {
  console.log(`Upload ${event.id} done, status ${event.responseCode}`);
});
const { id } = await FileTransfer.startUpload({
  url: 'https://example.tld/photos',
  path: photo.path!,
  mimeType: 'image/jpeg',
  maxRetries: 3,
});
```

`uploadType: 'binary'` with `method: 'PUT'` covers S3 presigned URLs, `network: 'unmetered'` holds the upload until Wi-Fi, and transfers are persisted, so `getTransfers()` still lists them after an app restart. [Announcing the Capacitor File Transfer Plugin](./announcing-the-capacitor-file-transfer-plugin.md) walks through the whole transfer lifecycle, including what happens when the OS kills the app mid-upload.

### Back to the gallery

Saving back to the device's gallery is the exception. `getPhoto()`'s `saveToGallery` option only saves what the Camera plugin itself captured, picked, or edited in that same call. It has no method for saving an arbitrary file, so a photo already run through Photo Manipulator can't be sent back to the gallery this way.

## Profile picture example

Here's the whole pipeline in one flow: capture a photo, crop it to a square, shrink it, and upload it as a profile picture.

```typescript
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import { PhotoManipulator } from '@capawesome/capacitor-photo-manipulator';
import { FileCompressor } from '@capawesome-team/capacitor-file-compressor';
import { Capacitor } from '@capacitor/core';

const setProfilePicture = async (userId: string) => {
  // 1. Capture or pick a photo
  const photo = await Camera.getPhoto({
    resultType: CameraResultType.Uri,
    source: CameraSource.Prompt,
  });

  // 2. Crop it to a centered square and resize it down
  const { width, height } = await PhotoManipulator.getInfo({ path: photo.path! });
  const side = Math.min(width, height);
  const { path: croppedPath } = await PhotoManipulator.transform({
    path: photo.path!,
    crop: { x: (width - side) / 2, y: (height - side) / 2, width: side, height: side },
    resize: { width: 512 },
  });

  // 3. Shrink the file before it leaves the device
  const { path: compressedPath } = await FileCompressor.compressImage({
    path: croppedPath,
    mimeType: 'image/jpeg',
    quality: 0.8,
  });

  // 4. Upload it
  const webPath = Capacitor.convertFileSrc(compressedPath);
  const blob = await (await fetch(webPath)).blob();
  const formData = new FormData();
  formData.append('file', blob, `${userId}.jpg`);
  await fetch('https://example.tld/profile-pictures', {
    method: 'POST',
    body: formData,
  });
};
```

Wrap the call site in a `try`/`catch` for the cancellation case (step 1), the same as any other `getPhoto()` call. Neither intermediate file gets copied to `Directory.Data` here, on purpose: both live in the cache (see [Upload the photo](#upload-the-photo) above), and that's fine, since each one is only ever a stepping stone to the upload in step 4. Swap step 4 for the [Firebase Storage snippet](#upload-the-photo) above if that's your backend; the crop and compress steps stay identical either way.

## Choosing an approach

The full pipeline is capture, then optionally view, then transform and optimize, then optionally hand off to an editor, then upload. The first fork is where the editing itself happens, and the official plugin covers more of it than its name suggests:

| Need | Camera plugin | Photo Manipulator | Photo Editor |
| --- | --- | --- | --- |
| User crops after capture | `allowEditing: true` (Android; iOS only from the camera) | No UI | In an installed app (Android) |
| Fixed crop or resize in code | `width`/`height` cap the size at capture | `transform(...)` with `crop` and `resize` | No |
| Upright output | `correctOrientation` (default `true`) | EXIF orientation applied on decode | Depends on the app |
| HEIC or AVIF to JPEG, PNG, WebP | No | `format` option (no WebP on iOS) | No |
| Platforms | Android, iOS, web | Android, iOS, web | Android |

Not every app needs all five steps:

- **Just need the photo uploaded?** Capture with `@capacitor/camera`, resize and convert with Photo Manipulator if it might be HEIC, compress with File Compressor, upload with Firebase Storage, a plain `fetch`, or File Transfer when the file is large. Skip the editor entirely.
- **Need a profile picture?** A fixed crop with Photo Manipulator works on every platform (see the [worked example](#profile-picture-example) above). If the user should pick the crop, `allowEditing: true` covers Android and iOS camera captures with no extra plugin, and Photo Editor is the Android option for a full editing app.
- **Letting a user attach several photos at once, like a gallery upload?** Use `pickImages()` instead of `getPhoto()`, then run the rest of the pipeline (Photo Manipulator, File Compressor, upload) over each photo in the returned array the same way.
- **Building a custom capture screen or continuous scanning?** `getPhoto()` isn't the right primitive (see [Live camera preview](#live-camera-preview)).
- **Need to show or fix a photo's GPS position, capture date, or camera info?** Reach for the Exif plugin, not Photo Manipulator; it reads and writes those tags directly without re-encoding the image.

## Best practices

- Wrap `getPhoto()` in a `try`/`catch`; a cancel is a rejection, not a null result (see [Capture a photo](#capture-a-photo)).
- Declare the iOS `Info.plist` keys before you ship; a missing `NSCameraUsageDescription` crashes the app instead of showing a permission prompt (see [Permissions](#permissions) above).
- Run HEIC/AVIF conversion before anything else touches the file. Handing a format the target can't decode to an `<img>` or a server produces a broken image or a decode error, not an exception you can catch.
- Compress with File Compressor after transforming with Photo Manipulator, not before. Compressing first and then cropping wastes the compression.
- Read any EXIF you need (capture date, GPS) with `readExif(...)` before transforming or compressing, since both steps drop it; then strip the rest with Photo Manipulator or `removeExif(...)` before the photo leaves the device.
- Gate Photo Editor behind a platform check and fall back to a Photo Manipulator crop on iOS.
- Decide whether a photo must outlive the session before you transform it; the copy to `Directory.Data` is cheapest right after capture, before Photo Manipulator and File Compressor add their own cache files.

## Related links

- [Capacitor Camera](https://capacitorjs.com/docs/apis/camera){:target="_blank"}: official plugin, captures or picks a photo.
- [Capacitor File Opener](../../sdks/capacitor/file-opener.md): opens the photo in the platform's own viewer.
- [Capacitor Photo Manipulator](../../sdks/capacitor/photo-manipulator.md): headless crop, resize, rotate, flip, and format conversion.
- [Capacitor Exif](../../sdks/capacitor/exif.md): reads, writes, and removes EXIF and GPS metadata without re-encoding.
- [Capacitor Photo Editor](../../sdks/capacitor/photo-editor.md): Android-only hand-off to an installed editing app.
- [Capacitor File Compressor](../../sdks/capacitor/file-compressor.md): shrinks the final file before upload or storage.
- [Capacitor File Transfer](../../sdks/capacitor/file-transfer.md): background uploads that survive the app being backgrounded.

## FAQ

### How can I take and edit photos in a Capacitor app?

Capture with the official `@capacitor/camera` plugin, then edit with one of two Capawesome plugins depending on what "edit" means for your app: [Capacitor Photo Manipulator plugin](../../sdks/capacitor/photo-manipulator.md) for headless crop, resize, rotate, flip, or format conversion with no UI, or [Capacitor Photo Editor plugin](../../sdks/capacitor/photo-editor.md) (Android-only) to hand the photo to an installed editing app for the user to touch up themselves.

### What permissions does the Capacitor Camera plugin need?

On iOS, `NSCameraUsageDescription` in `Info.plist` for capture and `NSPhotoLibraryUsageDescription` for picking from the gallery, plus `NSPhotoLibraryAddUsageDescription` if you save to the gallery. On Android, plain capture needs no manifest permissions at all; `READ_EXTERNAL_STORAGE` and `WRITE_EXTERNAL_STORAGE` are only required when `saveToGallery: true` is set.

### Why does getPhoto() throw an error when the user cancels?

That's expected, not a bug. Canceling the camera or picker rejects the promise with error code `OS-PLUG-CAMR-0006` ("Couldn't take photo because the process was canceled"), so `getPhoto()` needs a `try`/`catch` around it even in the common case.

### Can a user select more than one photo at a time?

Yes, with [`pickImages()`](https://capacitorjs.com/docs/apis/camera#pickimages){:target="_blank"} instead of `getPhoto()`. It returns a `GalleryPhotos` array of selected images from the gallery rather than a single result.

### Can I crop or resize a photo without opening an editor UI?

Yes. The [Capacitor Photo Manipulator plugin](../../sdks/capacitor/photo-manipulator.md) crops, resizes, rotates, and flips images entirely in code, with no editor interface shown to the user, on Android, iOS, and web.

### Can the Capacitor Camera plugin crop a photo on its own?

Yes, within limits. `allowEditing: true` on `getPhoto()` opens the platform's crop UI after capture on Android and iOS; on iOS it only works for `CameraSource.Camera`, not for photos picked from the library. `width` and `height` cap the saved dimensions while keeping the aspect ratio. Anything beyond that (a fixed crop in code, rotation, format conversion) needs Photo Manipulator.

### How do I convert HEIC photos from an iPhone to JPEG in Capacitor?

Photo Manipulator converts HEIC and AVIF images to JPEG, PNG, or WebP using the platform's native decoders. It applies the EXIF orientation during decoding so the output is upright, and strips metadata from the result.

### How do I read a photo's GPS location or camera info in Capacitor?

With the [Capacitor Exif plugin](../../sdks/capacitor/exif.md). It reads typed EXIF tags, including GPS position, camera model, and capture date, without re-encoding the image, and can write or remove individual tags the same way, including on HEIC files.

### Is there a built-in photo editor for iOS in Capacitor?

No. The [Capacitor Photo Editor plugin](../../sdks/capacitor/photo-editor.md) that hands a photo to an installed editing app is Android-only. For iOS, use [Capacitor Photo Manipulator plugin](../../sdks/capacitor/photo-manipulator.md) for a fixed crop or transform instead of an interactive editor.

### Does Capacitor have a live camera preview?

Not through `@capacitor/camera`. `getPhoto()` opens the platform camera UI and returns one photo after it closes; it isn't a live feed. A continuous live preview inside your own UI needs a native camera view, a separate integration from calling the plugin.

### Does editing a photo change its EXIF and GPS metadata?

Photo Manipulator strips all metadata, including EXIF and GPS coordinates, from its output by re-encoding the image, and File Compressor drops EXIF the same way. Photo Editor's behavior depends on the third-party editing app the user picks, since Capacitor only hands the photo off and doesn't control what that app does to it.

### Is the photo from getPhoto() saved permanently?

Not necessarily. Capacitor doesn't document it as persistent, and the file lives in the app's cache directory, which the OS can clear under storage pressure. Copy it to `Directory.Data` with `@capacitor/filesystem` if it needs to survive past the current session.

### Can I save an edited photo back to the device's gallery?

Not with `saveToGallery`. That option only saves what the Camera plugin itself captured, picked, or edited in that same call; it has no method for saving an arbitrary file, so a photo already processed by Photo Manipulator can't be routed back to the gallery through it.

### Do I need a new app release to add photo editing?

Yes. Camera, File Opener, Photo Manipulator, Photo Editor, Exif, File Compressor, and File Transfer are all native plugins. Adding one that isn't already in your app requires a new native build and a store review; it isn't something a Live Update can ship.

## Try Capawesome Cloud

That native build has to come from somewhere. Capawesome Cloud builds and ships it without a Mac or a local Xcode setup; see [How to Sign & Build Capacitor Apps in the Cloud](./how-to-sign-and-build-your-capacitor-app-in-the-cloud.md) for the exact steps.

[Try Capawesome Cloud Free](https://capawesome.io){ .md-button .md-button--primary }

## Conclusion

Start with what the app needs: most apps need capture, a resize or HEIC conversion, and an upload, which is `@capacitor/camera`, Photo Manipulator, and File Compressor with no editor at all. Add Photo Editor only if you specifically want Android users to touch up a photo in an app they already have installed, and build the iOS path separately. For everything around the file once it exists (reading it as a blob, moving it, sharing it), [Capacitor File Handling: The Complete Guide](./capacitor-file-handling-guide.md) picks up where this post stops.

Questions about a specific setup? Join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW){:target="_blank"}, and [subscribe to the Capawesome newsletter](https://capawesome.io/newsletter/){:target="_blank"} for updates on new plugins and guides like this one.
