Untuk mengambil dan mengedit foto di aplikasi Capacitor, gunakan @capacitor/camera 8.1 atau lebih baru: Camera.takePhoto() membuka kamera, editable: 'in-app' menambahkan langkah crop dan rotate, dan chooseFromGallery() memilih foto yang sudah ada melalui system picker. Untuk layar kamera kustom dengan kontrol sendiri, gunakan @capgo/camera-preview. Panduan ini membahas pengambilan, pengeditan, izin, kompresi, metadata, penyimpanan, dan upload, dengan code untuk Capacitor 8.
Pilih tool yang tepat
| Butuh | Plugin | Mengapa |
|---|---|---|
| Ambil foto dengan UI kamera sistem | @capacitor/camera takePhoto |
Tidak perlu maintain UI kustom, jalan di mana saja |
| Biarkan user crop atau rotate | @capacitor/camera editable, editPhoto, editURIPhoto |
Editor in-app bawaan di iOS dan Android |
| Pilih satu atau beberapa foto atau video | @capacitor/camera chooseFromGallery |
Pakai Android Photo Picker dan PHPicker, tanpa izin akses penuh |
| Layar kamera kustom (overlay, bingkai kartu ID, panduan wajah) | @capgo/camera-preview |
Preview native yang Anda kontrol dari HTML dan JS |
| Telusuri seluruh galeri di dalam grid Anda sendiri | @capgo/capacitor-photo-library |
Akses galeri dengan pagination dan thumbnail |
| Perkecil, ubah ukuran, atau konversi HEIC | @capgo/capacitor-file-compressor |
Encoder native, hapus EXIF |
| Upload foto besar dengan andal | @capgo/capacitor-uploader |
Native background upload dengan progress |
Instal dan konfigurasi plugin Camera
bun add @capacitor/camera
bunx cap sync
Pastikan versi Anda 8.1 atau lebih baru. Metode dalam panduan ini (takePhoto, chooseFromGallery, editPhoto, editURIPhoto) ditambahkan di 8.1, dan CameraErrorCode di 8.2. Versi lama getPhoto dan pickImages sudah deprecated.
iOS
Tambahkan key ini ke ios/App/App/Info.plist. Apple menolak build dengan string yang tidak jelas, jadi jelaskan alasan sebenarnya:
<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
Capture dan pemilihan galeri tidak butuh izin. Plugin ini memulai activity kamera sistem dan memakai Android Photo Picker pada perangkat Android 11+ yang mendukungnya, dengan fallback ke ACTION_OPEN_DOCUMENT lainnya. Untuk mendapatkan backported picker pada perangkat lama dengan Google Play services, tambahkan ini di dalam <application> di AndroidManifest.xml. Snippet ini menggunakan tools namespace, jadi tambahkan juga xmlns:tools="http://schemas.android.com/tools" ke elemen root <manifest> jika belum ada (proyek Capacitor baru hanya mendeklarasikan 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>
Hanya saveToGallery: true butuh izin storage, dan hanya pada versi Android lama:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
Hindari menambahkan READ_MEDIA_IMAGES kecuali jika fungsi utama aplikasi Anda adalah galeri atau editor. Kebijakan izin foto dan video Google Play mewajibkan aplikasi yang hanya sesekali butuh foto untuk menggunakan picker, dan build yang mendeklarasikan izin tanpa alasan jelas akan ditolak.
Ambil foto
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;
}
}
Hasil yang Anda dapatkan adalah MediaResult:
uri: native file URI dari gambar lengkap (bukan di web). Teruskan ke Filesystem, uploader, ataueditURIPhoto.webPath: URL yang bisa dimuat WebView. Gunakan untuk preview alih-alih base64.thumbnail: thumbnail base64 di native, gambar lengkap di web.metadata: ukuran, format, resolusi, tanggal pembuatan, dan EXIF saatincludeMetadatabernilai true.saved: apakahsaveToGalleryberhasil.
targetWidth dan targetHeight harus disetel bersamaan. Keduanya adalah cara termurah untuk menghindari upload foto 12 MP saat Anda hanya membutuhkan gambar berukuran 2048 piksel.
Edit foto: pangkas dan putar
Edit langsung setelah memotret
Atur editable dan pengguna masuk ke editor sebelum promise ter-resolve:
const photo = await Camera.takePhoto({
quality: 85,
editable: 'in-app', // 'in-app' | 'external' | 'no'
});
'external' menyerahkan gambar ke editor lain yang terinstal di Android (misalnya Google Photos) dan kembali menggunakan editor dalam aplikasi. iOS tidak punya editing eksternal dan selalu menggunakan 'in-app'.
Edit foto yang sudah ada
Untuk mengedit foto yang dipilih atau yang sudah Anda simpan sebelumnya, berikan URI-nya:
const edited = await Camera.editURIPhoto({
uri: photo.uri!,
saveToGallery: false,
includeMetadata: false,
});
Jika Anda hanya punya base64 (misalnya dari canvas atau respons server), gunakan editPhoto. Input harus berupa raw base64 tanpa data: prefix:
const { outputImage } = await Camera.editPhoto({ inputImage: base64 });
img.src = `data:image/jpeg;base64,${outputImage}`;
Kedua metode ini tidak tersedia di web. Jika Anda butuh filter, stiker, teks, atau menggambar bebas, editor bawaan tidak cukup. Render gambar di dalam <canvas> dan terapkan edit di sana, atau gunakan cropper JavaScript seperti Cropper.js untuk rasio aspek tetap seperti avatar. Ekspor canvas (canvas.toBlob(cb, 'image/jpeg', 0.85)) menghasilkan Blob yang bisa langsung di-upload.
Pilih foto dari galeri
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);
Untuk satu foto, allowMultipleSelection: false plus editable: 'in-app' memberi Anda pick-then-crop dalam satu panggilan. Atur mediaType: MediaTypeSelection.All untuk menerima video juga.
Di iOS, pengguna dapat memberikan akses terbatas. pickLimitedLibraryPhotos() memungkinkan mereka mengubah pilihan dan getLimitedLibraryPhotos() mengembalikan apa yang bisa Anda lihat. Dengan sistem picker, Anda jarang membutuhkan keduanya, karena item yang dipilih selalu dapat dibaca.
Membangun grid galeri Anda sendiri
Jika aplikasi Anda menampilkan pustaka pengguna di dalam UI Anda sendiri (aplikasi cetak foto, aplikasi backup), gunakan @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
}
dari plugin yang sama pickMedia() membuka sistem picker dan menyalin pilihan ke cache, jika Anda menginginkan satu plugin untuk kedua alur. Ingat kebijakan Google Play di atas sebelum meminta akses pustaka penuh di Android.
Buat layar kamera kustom
UI kamera sistem tidak bisa menampilkan frame ID card, outline dokumen, atau brand Anda. @capgo/camera-preview merender native camera feed dan memungkinkan Anda menggambar UI menggunakan 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();
Dengan toBack: true, preview berada di belakang WebView, jadi halaman dan setiap parent element harus memiliki background transparan saat kamera aktif. Pola yang umum adalah camera-active class pada body yang mengatur background: transparent dan menyembunyikan sisa aplikasi. Tambahkan NSCameraUsageDescription pada iOS, dan pada Android plugin meminta CAMERA runtime permission melalui requestPermissions(). Selalu panggil stop() saat layar ditutup untuk melepaskan kamera.
Tampilkan, kompres, dan konversi
Gunakan webPath (atau Capacitor.convertFileSrc(path) untuk path dari plugin lain) untuk menampilkan gambar. Hindari base64 di <img> tag untuk foto ukuran penuh: JPEG 4 MB menjadi string 5,3 MB di memori JS dan melewati bridge.
Sebelum upload, perkecil ukuran gambar:
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',
});
Kompresor menerima input HEIC dari iPhone, jadi pemanggilan yang sama mengonversi HEIC ke JPEG untuk backend yang tidak bisa membaca HEIC. Hasilnya ditulis ke direktori sementara, jadi pindahkan dengan @capacitor/filesystem jika Anda perlu menyimpannya.
Metadata EXIF dan privasi lokasi
Dengan includeMetadata: true, metadata.exif berisi blok EXIF dari foto tersebut. Foto dari camera roll bisa menyertakan koordinat GPS. Dua aturan ini menjaga Anda dari masalah:
- Hapus lokasi sebelum upload foto yang dipublikasikan (foto profil, listing marketplace).
@capgo/capacitor-file-compressormenghapus EXIF di semua platform sebagai bagian dari kompresi. - Jika Anda butuh lokasi (laporan lapangan, inspeksi), baca dari EXIF sebelum kompresi dan kirim sebagai field terpisah. Lalu cantumkan di label privasi App Store dan formulir data safety Play Store Anda. Lihat panduan privacy manifest kami kami untuk sisi iOS.
Simpan ke galeri atau penyimpanan aplikasi
saveToGallery: true aktif takePhoto atau editURIPhoto menyimpan gambar ke camera roll dan menetapkan saved pada hasilnya. Agar foto tetap privat di aplikasi Anda, salin ke penyimpanan aplikasi:
import { Filesystem, Directory } from '@capacitor/filesystem';
await Filesystem.copy({
from: photo.uri!,
to: `photos/${Date.now()}.jpg`,
toDirectory: Directory.Data,
});
File di direktori cache dapat dihapus oleh OS kapan saja, jadi jangan simpan referensi jangka panjang ke file tersebut.
Upload the photo
Untuk foto berukuran kecil, fetch dengan FormData sudah cukup:
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 });
Untuk file besar atau jaringan tidak stabil, gunakan native uploader yang tetap berjalan saat aplikasi di-background:
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);
});
Contoh: alur foto profil
- Tampilkan action sheet dengan “Ambil foto” dan “Pilih dari galeri”.
- Panggil
takePhotoatauchooseFromGallerydenganeditable: 'in-app',targetWidth: 1024,targetHeight: 1024. - Crop ke bentuk persegi di canvas kalau butuh rasio 1:1 yang pasti.
- Kompres dengan
quality: 0.8dan EXIF dihapus. - Upload, lalu tampilkan URL server, bukan file lokal, agar avatar tetap ada setelah reinstall.
Pemecahan Masalah
App crash di iOS saat kamera dibuka: key usage description hilang di Info.plist.
Foto berputar di Android: atur correctOrientation: true (default) dan hindari membaca piksel mentah tanpa menerapkan orientasi EXIF.
Aplikasi restart setelah mengambil foto di perangkat Android dengan memori rendah: Android mungkin mematikan activity Anda saat kamera terbuka. Dengarkan appRestoredResult dari @capacitor/app untuk memulihkan hasil setelah restart.
webPath tidak dimuat: Anda menyimpan webPath dan menggunakannya kembali setelah restart. Simpan uri atau path Filesystem lalu konversi lagi dengan Capacitor.convertFileSrc.
Pratinjau kamera menampilkan layar hitam: elemen di atas WebView masih punya warna latar, atau start() dipanggil sebelum izinnya diberikan.
Play Console menolak build karena izin foto: hapus READ_MEDIA_IMAGES dan READ_MEDIA_VIDEO kecuali Anda sudah mengajukan deklarasi bahwa aplikasi memerlukan akses luas.
Rilis perbaikan tanpa lewat store
Ukuran gambar, level kompresi, rasio crop, dan endpoint upload semuanya ada di web code. Dengan Live updates Capgo Anda bisa mengubahnya untuk setiap user tanpa menunggu review, sementara perubahan native seperti plugin baru tetap melalui build reguler. Jika Anda butuh build iOS tanpa Mac, Build Capgo menangani signing dan upload dengan Xcode 26, yang diwajibkan App Store Connect sejak April 2026.