Pour prendre et retoucher des photos dans une app Capacitor, utilisez @capacitor/camera 8.1 ou version ultérieure : Camera.takePhoto() ouvre l’appareil photo, editable: 'in-app' ajoute une étape de recadrage et de rotation, et chooseFromGallery() sélectionne des photos existantes via le sélecteur système. Pour un écran d’appareil photo personnalisé avec vos propres contrôles, utilisez @capgo/camera-preview. Ce guide aborde la capture, l’édition, les autorisations, la compression, les métadonnées, l’enregistrement et l’envoi, avec code pour Capacitor 8.
Choisissez le bon outil
| Besoin | Plugin | Pourquoi |
|---|---|---|
| Prendre une photo avec l’interface caméra du système | @capacitor/camera takePhoto |
Aucune interface personnalisée à maintenir, fonctionne partout |
| Laisser l’utilisateur recadrer ou pivoter | @capacitor/camera editable, editPhoto, editURIPhoto |
Éditeur intégré dans l’app sur iOS et Android |
| Sélectionner une ou plusieurs photos ou vidéos | @capacitor/camera chooseFromGallery |
Utilise le Photo Picker d’Android et PHPicker, sans permission large |
| Custom camera screen (overlays, ID card frame, face guide) | @capgo/camera-preview |
Aperçu natif que vous contrôlez depuis HTML et JS |
| Parcourez toute la bibliothèque dans votre propre grille | @capgo/capacitor-photo-library |
Accès paginé à la bibliothèque avec miniatures |
| Réduire, redimensionner ou convertir HEIC | @capgo/capacitor-file-compressor |
Encodeur natif, supprime l’EXIF |
| Envoyer de grandes photos de manière fiable | @capgo/capacitor-uploader |
Upload natif en arrière-plan avec progression |
Installez et configurez le plugin Camera
bun add @capacitor/camera
bunx cap sync
Assurez-vous d’avoir la version 8.1 ou plus récente. Les méthodes de ce guide (takePhoto, chooseFromGallery, editPhoto, editURIPhoto) ont été ajoutées en 8.1, et CameraErrorCode en 8.2. Les anciennes getPhoto et pickImages sont dépréciées.
iOS
Ajoutez ces clés à ios/App/App/Info.plist. Apple rejette les builds avec des chaînes vagues, donc expliquez la raison réelle :
<key>NSCameraUsageDescription</key>
<string>Take a photo for your profile or attach it to a report.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Choose existing photos to attach.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Save photos you take to your library.</string>
Android
La capture et la sélection depuis la galerie ne nécessitent aucune autorisation. Le plugin lance l’activité caméra du système et utilise le sélecteur de photos Android sur les appareils Android 11+ qui le prennent en charge, avec repli sur ACTION_OPEN_DOCUMENT ailleurs. Pour obtenir le sélecteur rétroporté sur les anciens appareils disposant des services Google Play, ajoutez ceci dans <application> dans AndroidManifest.xml. L’extrait utilise le tools namespace, donc ajoutez également xmlns:tools="http://schemas.android.com/tools" à la racine <manifest> élément s'il n'existe pas encore (un nouveau projet Capacitor déclare uniquement xmlns:android):
<service android:name="com.google.android.gms.metadata.ModuleDependencies"
android:enabled="false"
android:exported="false"
tools:ignore="MissingClass">
<intent-filter>
<action android:name="com.google.android.gms.metadata.MODULE_DEPENDENCIES" />
</intent-filter>
<meta-data android:name="photopicker_activity:0:required" android:value="" />
</service>
Seules les saveToGallery: true needs storage permissions, and only on old Android versions:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
Évitez d'ajouter READ_MEDIA_IMAGES sauf si le cœur de votre application est une galerie ou un éditeur. La politique de Google Play sur les permissions photo et vidéo impose aux applications ayant des besoins ponctuels en photos d'utiliser le sélecteur ; les builds qui déclarent la permission sans justification sont rejetés.
Prendre une photo
import { Camera, CameraDirection, CameraErrorCode, EncodingType } from '@capacitor/camera';
export async function takePicture() {
try {
const photo = await Camera.takePhoto({
quality: 85,
targetWidth: 2048,
targetHeight: 2048,
correctOrientation: true,
encodingType: EncodingType.JPEG,
cameraDirection: CameraDirection.Rear, // iOS and web
includeMetadata: true,
});
// webPath works directly in <img src>
document.querySelector<HTMLImageElement>('#preview')!.src = photo.webPath!;
return photo;
} catch (e: any) {
if (e.code === CameraErrorCode.TakePhotoCancelled) return null;
if (e.code === CameraErrorCode.CameraPermissionDenied) {
// show a screen that explains how to enable the camera in Settings
return null;
}
throw e;
}
}
Ce que vous récupérez est un MediaResult:
uri: URI de fichier natif de l'image complète (pas sur le web). Transmettez-l’à Filesystem, à un uploader, oueditURIPhoto.webPath: une URL que la WebView peut charger. Utilisez-la pour les aperçus plutôt que le base64.thumbnail: une vignette base64 en natif, l'image complète sur le web.metadata: taille, format, résolution, date de création et EXIF lorsqueincludeMetadataest à true.saved: sisaveToGallerya réussi.
targetWidth et targetHeight doivent être définis ensemble. C'est la méthode la plus économique pour éviter d'uploader des photos de 12 MP quand vous n'avez besoin que d'une image de 2048 pixels.
Modifier la photo : recadrer et pivoter
Modifier juste après la capture
Définissez editable et l'utilisateur se retrouve dans un éditeur avant que la promesse ne soit résolue :
const photo = await Camera.takePhoto({
quality: 85,
editable: 'in-app', // 'in-app' | 'external' | 'no'
});
'external' transmet l'image à un autre éditeur installé sur Android (par exemple Google Photos) et bascule sur l'éditeur intégré. iOS ne propose pas d'édition externe et utilise toujours 'in-app'.
Modifier une photo existante
Pour éditer une photo sélectionnée ou que vous avez stockée précédemment, passez son URI :
const edited = await Camera.editURIPhoto({
uri: photo.uri!,
saveToGallery: false,
includeMetadata: false,
});
Si vous ne disposez que de base64 (par exemple depuis un canvas ou une réponse serveur), utilisez editPhoto. L'entrée doit être du base64 brut sans data: préfixe :
const { outputImage } = await Camera.editPhoto({ inputImage: base64 });
img.src = `data:image/jpeg;base64,${outputImage}`;
Aucune des deux méthodes n'est disponible sur le web. Si vous avez besoin de filtres, de stickers, de texte ou de dessin libre, l'éditeur intégré ne suffit pas. Affichez l'image dans un <canvas> et appliquer les modifications, ou utiliser un outil de recadrage JavaScript comme Cropper.js pour des ratios fixes (avatars, par exemple). L’export via canvas («canvas.toBlob(cb, 'image/jpeg', 0.85)) vous fournit un Blob que vous pouvez envoyer directement.
Choisir des photos dans la galerie
import { Camera, MediaTypeSelection } from '@capacitor/camera';
const { results } = await Camera.chooseFromGallery({
mediaType: MediaTypeSelection.Photo,
allowMultipleSelection: true,
limit: 5, // Android 13+ and iOS
quality: 85,
targetWidth: 2048,
targetHeight: 2048,
});
const urls = results.map((r) => r.webPath);
Pour une seule photo, allowMultipleSelection: false , ce qui editable: 'in-app' gives you pick-then-crop in one call. Set mediaType: MediaTypeSelection.All pour accepter aussi les vidéos.
On iOS, users can grant limited access. pickLimitedLibraryPhotos() leur permet de modifier la sélection et getLimitedLibraryPhotos() retourne ce que vous voyez. Avec le sélecteur système, vous avez rarement besoin de l’un ou l’autre, car les éléments sélectionnés sont toujours lisibles.
Créer votre propre grille de galerie
If your app shows the user’s library inside your own UI (a photo printing app, a backup app), use @capgo/capacitor-photo-library:
import { PhotoLibrary } from '@capgo/capacitor-photo-library';
const { state } = await PhotoLibrary.requestAuthorization();
if (state === 'authorized' || state === 'limited') {
const { assets, hasMore } = await PhotoLibrary.getLibrary({
limit: 60,
offset: 0,
thumbnailWidth: 256,
thumbnailHeight: 256,
});
// assets[i].thumbnail?.webPath for the grid, getPhotoUrl({ id }) for full size
}
Le même plugin pickMedia() ouvre le sélecteur système et copie la sélection dans le cache, si vous souhaitez un seul plugin pour les deux flux. Rappelez-vous la politique Google Play mentionnée ci-dessus avant de demander l’accès complet à la bibliothèque sur Android.
Créer un écran caméra personnalisé
L’interface caméra système ne peut pas afficher un cadre de carte d’identité, un contour de document ou votre marque. @capgo/camera-preview affiche le flux caméra natif et vous permet de dessiner l’interface en HTML :
bun add @capgo/camera-preview
bunx cap sync
import { CameraPreview } from '@capgo/camera-preview';
await CameraPreview.start({
position: 'rear',
toBack: true, // preview behind the WebView
aspectRatio: '4:3',
storeToFile: true, // capture() returns a file path instead of base64
disableAudio: true,
});
await CameraPreview.setFlashMode({ flashMode: 'auto' });
await CameraPreview.setZoom({ level: 2 });
const { value: filePath, exif } = await CameraPreview.capture({ quality: 85 });
await CameraPreview.stop();
Avec toBack: true, l’aperçu se situe derrière le WebView, donc la page et tous les éléments parents doivent avoir un fond transparent pendant que la caméra est active. Un schéma courant consiste à utiliser un camera-active classe sur body qui définit background: transparent et masque le reste de l'app. Ajoutez NSCameraUsageDescription sur iOS, et sur Android le plugin demande la CAMERA permission runtime via requestPermissions(). Appelez toujours stop() lors de la fermeture de l'écran pour libérer la caméra.
Affichez, compressez et convertissez
Utiliser webPath (ou Capacitor.convertFileSrc(path) pour les chemins issus d'autres plugins) afin d'afficher des images. Évitez le base64 dans <img> tags pour les photos plein format : un JPEG de 4 Mo devient une chaîne de 5,3 Mo en mémoire JS et traverse le bridge.
Avant l'upload, réduisez la taille de l'image :
import { FileCompressor } from '@capgo/capacitor-file-compressor';
const { path } = await FileCompressor.compressImage({
path: photo.uri!,
width: 1600, // keeps aspect ratio
quality: 0.75,
mimeType: 'image/jpeg',
});
Le compresseur accepte les fichiers HEIC des iPhones : le même appel convertit donc le HEIC en JPEG pour les backends qui ne savent pas le lire. Il écrit le résultat dans un répertoire temporaire, déplacez-l’avec @capacitor/filesystem si vous devez le conserver.
EXIF metadata and location privacy
Avec includeMetadata: true, metadata.exif contient le bloc EXIF de la photo. Les photos de la galerie peuvent inclure des coordonnées GPS. Deux règles pour éviter les ennuis :
- Supprimez la localisation avant d'uploader des photos qui deviennent publiques (photos de profil, annonces de marketplace).
@capgo/capacitor-file-compressorsupprime les EXIF sur toutes les plateformes lors de la compression. - Si vous avez besoin de la localisation (rapports de terrain, inspections), lisez-la depuis les EXIF avant de compresser et envoyez-la dans un champ séparé. Déclarez-l’ensuite dans l’étiquette de confidentialité de l’App Store et le formulaire de sécurité des données de Play. Consultez notre guide du manifeste de confidentialité pour la partie iOS.
Enregistrer dans la galerie ou le stockage de l'app
saveToGallery: true sur takePhoto ou editURIPhoto enregistre l'image dans la pellicule et définit saved sur le résultat. Pour que la photo reste privée dans votre application, copiez-la dans le stockage de l'app :
import { Filesystem, Directory } from '@capacitor/filesystem';
await Filesystem.copy({
from: photo.uri!,
to: `photos/${Date.now()}.jpg`,
toDirectory: Directory.Data,
});
Les fichiers du répertoire cache peuvent être supprimés par l'OS à tout moment, n'y stockez donc pas de références persistantes.
Envoyer la photo
Pour les petites photos, fetch par FormData est suffisant :
const blob = await fetch(photo.webPath!).then((r) => r.blob());
const form = new FormData();
form.append('file', blob, 'photo.jpg');
await fetch('https://api.example.com/photos', { method: 'POST', body: form });
Pour les fichiers volumineux ou les réseaux instables, utilisez un uploader natif qui continue même quand l'app passe en arrière-plan :
import { Uploader } from '@capgo/capacitor-uploader';
const { id } = await Uploader.startUpload({
filePath: path,
serverUrl: 'https://api.example.com/photos',
method: 'POST',
mimeType: 'image/jpeg',
});
await Uploader.addListener('events', (event) => {
console.log(event.name, event.payload);
});
Exemple : flux de photo de profil
- Afficher un action sheet avec « Prendre une photo » et « Choisir dans la bibliothèque ».
- Appelez
takePhotoouchooseFromGallerypareditable: 'in-app',targetWidth: 1024,targetHeight: 1024. - Recadrez en carré dans un canvas si vous avez besoin d'un ratio 1:1 garanti.
- Compressez avec
quality: 0.8et les données EXIF supprimées. - Faites l'upload, puis affichez l'URL du serveur plutôt que le fichier local, pour que l'avatar persiste après une réinstallation.
Troubleshooting
L'application plante sur iOS à l'ouverture de la caméra: une clé de description d'utilisation est manquante dans Info.plist.
La photo est tournée sur Android: définissez correctOrientation: true (par défaut) et évitez de lire les pixels bruts sans appliquer l'orientation EXIF.
L'application redémarre après la prise d'une photo sur les appareils Android à faible mémoire: Android peut tuer votre activité pendant que la caméra est ouverte. Écoutez appRestoredResult depuis @capacitor/app pour récupérer le résultat après le redémarrage.
webPath ne charge pas: vous avez sauvegardé le webPath et l'avez réutilisé après un redémarrage. Persistez le uri ou un chemin Filesystem et convertissez-l’à nouveau avec Capacitor.convertFileSrc.
L'aperçu de la caméra affiche un écran noir: un élément au-dessus de la WebView a encore une couleur d'arrière-plan, ou start() a été appelé avant que la permission ne soit accordée.
Play Console rejette le build pour les permissions photo: supprimer READ_MEDIA_IMAGES et READ_MEDIA_VIDEO sauf si vous avez déclaré que votre application a besoin d’un accès étendu.
Déployer des correctifs sans passer par les stores
Les tailles d’image, niveaux de compression, ratios de recadrage et endpoints d’upload sont tous dans votre web code. Avec les live updates Capgo vous pouvez les modifier pour tous les utilisateurs sans attendre de validation, tandis que les modifications natives comme de nouveaux plugins passent par un build classique. Si vous avez besoin de builds iOS sans Mac, Build Capgo gère la signature et les uploads avec Xcode 26, exigé par App Store Connect depuis avril 2026.