ImagePickerKMP v1.0.35
GitHub
NEW v1.0.35 — fileSize now in bytes, improved WASM support
Open Source • MIT License

ImagePickerKMP

The most complete camera & image picker library for Kotlin Multiplatform. One unified API for Android, iOS, Desktop, Web and WASM.

Android API 21+ iOS 12.0+ Desktop JVM JS / Web WASM

Camera Capture

Native camera with flash, rotation, zoom, crop and compression on Android & iOS.

Gallery Picker

Single & multiple selection, MIME filtering, selection limit, EXIF extraction.

Crop & Edit

Free, square and circular crop with zoom, rotation and aspect ratio lock.

EXIF Metadata

GPS, altitude, camera model, ISO, aperture, focal length — Android & iOS.

Cloud OCR

Gemini, OpenAI, Claude, Azure, Ollama and custom endpoints. All platforms.

Compression

LOW / MEDIUM / HIGH quality levels with async processing and bitmap recycling.

PDF & Format Support

JPEG, PNG, HEIC, HEIF, WebP, GIF, BMP and PDF across all platforms.

UI Customization

Custom button colors, icons, permission dialogs, confirmation screens and camera callbacks.


Requirements

RequirementMinimum VersionNotes
Kotlin2.3.20Breaking — ABI incompatible with < 2.3.x
Compose Multiplatform1.10.3Requires Kotlin 2.3.x
Android minSdk24
Android compileSdk36
iOS12.0+
JDK (Desktop)11+
Kotlin version is mandatory. Projects using Kotlin < 2.3.x will fail with ABI version incompatible. If you need Kotlin 2.1.x, use a previous release.

Installation

Kotlin Multiplatform

build.gradle.kts
// commonMain dependencies
implementation("io.github.ismoy:imagepickerkmp:1.0.35")

React / JavaScript (NPM)

terminal
npm install imagepickerkmp
Always wrap launchers in a visible container. Place ImagePickerLauncher inside a Box(Modifier.fillMaxSize()) — otherwise the camera preview won't render.

Correct vs Incorrect Usage

Correct
Box(Modifier.fillMaxSize()) {
    if (showCamera) {
        ImagePickerLauncher(
            config = ImagePickerConfig(...)
        )
    }
}
Incorrect — camera not visible
// No container — preview invisible
if (showCamera) {
    ImagePickerLauncher(
        config = ImagePickerConfig(...)
    )
}

Permissions Setup

<!-- Info.plist -->
<key>NSCameraUsageDescription</key>
<string>Camera access to capture photos</string>

<key>NSPhotoLibraryUsageDescription</key>
<string>Photo library access to select images</string>

<key>NSPhotoLibraryAddUsageDescription</key>
<string>Save captured photos to your library</string>

<!-- Required when includeExif = true -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>Location for photo geotagging</string>
<!-- AndroidManifest.xml -->
<!-- No permissions required — the library manages them automatically -->

<!-- Optional: declare CAMERA only if your app directly uses the camera -->
<uses-permission
    android:name="android.permission.CAMERA"
    android:required="false" />
Zero config on Android. The library auto-manages camera and media store permissions internally. No manifest entries are needed. The CAMERA permission is optional — add it only if your app directly accesses the camera outside of this library.

Camera Capture

Use ImagePickerLauncher to open the native camera. Control flash, skip confirmation, add crop and set compression.

Basic Camera

Kotlin
var showCamera by remember { mutableStateOf(false) }
var photo by remember { mutableStateOf<PhotoResult?>(null) }

Box(modifier = Modifier.fillMaxSize()) {
    if (showCamera) {
        ImagePickerLauncher(
            config = ImagePickerConfig(
                onPhotoCaptured = { result ->
                    photo = result
                    showCamera = false
                },
                onError = { showCamera = false },
                onDismiss = { showCamera = false }
            )
        )
    }
    Button(onClick = { showCamera = true }) {
        Text("Open Camera")
    }
}

Camera with Compression & Crop

Kotlin
ImagePickerLauncher(
    config = ImagePickerConfig(
        onPhotoCaptured = { result -> /* handle result */ },
        onDismiss = { showCamera = false },
        cameraCaptureConfig = CameraCaptureConfig(
            compressionLevel = CompressionLevel.MEDIUM,
            skipConfirmation = false,
            includeExif = true,
            cropConfig = CropConfig(
                enabled = true,
                circularCrop = true,
                squareCrop = true,
                freeformCrop = true
            )
        )
    )
)

CameraCaptureConfig Parameters

ParameterTypeDefaultDescription
compressionLevelCompressionLevel?nullImage compression level
skipConfirmationBooleanfalseSkip preview/confirm screen
includeExifBooleanfalseExtract EXIF metadata
cropConfigCropConfig?nullCrop configuration
mimeTypesList<MimeType>[IMAGE_ALL]Allowed MIME types


Image Crop

Built-in crop UI with free, square and circular modes plus zoom and rotation controls. Works on Android, iOS, Desktop and Web.

Kotlin
ImagePickerLauncher(
    config = ImagePickerConfig(
        onPhotoCaptured = { result -> photo = result },
        onDismiss = { showCamera = false },
        cameraCaptureConfig = CameraCaptureConfig(
            cropConfig = CropConfig(
                enabled = true,
                circularCrop = true,  // show circle crop button
                squareCrop = true,    // show square crop button
                freeformCrop = true   // show free-form crop button
            )
        )
    )
)
ParameterTypeDefaultDescription
enabledBooleanfalseEnable crop UI after capture
circularCropBooleanfalseShow circular crop option
squareCropBooleanfalseShow square crop option
freeformCropBooleantrueShow free-form crop option

Image Compression

Automatic background compression with configurable levels. Works for camera and gallery on Android and iOS.

Kotlin
// Camera with compression
ImagePickerLauncher(
    config = ImagePickerConfig(
        cameraCaptureConfig = CameraCaptureConfig(
            compressionLevel = CompressionLevel.HIGH
        )
    )
)

// Gallery with compression
GalleryPickerLauncher(
    compressionLevel = CompressionLevel.MEDIUM,
    onPhotosSelected = { /* compressed photos */ },
    onError = { },
    onDismiss = { }
)
LevelJPEG QualityMax DimensionUse Case
LOW85%3840 px (4K)Near-lossless, large files
MEDIUM70%1920 px (FHD)Balanced quality/size
HIGH50%1280 px (HD)Maximum size reduction
Default compression is MEDIUM. CameraCaptureConfig uses CompressionLevel.MEDIUM by default. Set to null to disable compression entirely and get the original full-quality image.

EXIF Metadata

Extract rich metadata from photos on Android and iOS. Requires includeExif = true.

Kotlin
ImagePickerLauncher(
    config = ImagePickerConfig(
        onPhotoCaptured = { result ->
            result.exif?.let { exif ->
                println("GPS: ${exif.latitude}, ${exif.longitude}")
                println("Camera: ${exif.cameraModel}")
                println("Date: ${exif.dateTaken}")
                println("Flash: ${exif.flash}")
                println("ISO: ${exif.iso}")
                println("Exposure: ${exif.exposureTime}")
                println("Size: ${exif.imageWidth} x ${exif.imageHeight} px")
            }
        },
        cameraCaptureConfig = CameraCaptureConfig(includeExif = true)
    )
)

// Gallery EXIF (iOS requires photo library permission)
GalleryPickerLauncher(
    config = GalleryPickerConfig(includeExif = true),
    onPhotosSelected = { photos ->
        photos.forEach { photo ->
            val gps = "${photo.exif?.latitude}, ${photo.exif?.longitude}"
        }
    },
    onError = { },
    onDismiss = { }
)
FieldTypeDescription
GPS
latitudeDouble?GPS latitude — redacted by default (redactGpsData = true)
longitudeDouble?GPS longitude — redacted by default
altitudeDouble?GPS altitude in meters — redacted by default
Date & Time
dateTakenString?Date and time photo was taken
dateTimeString?General date/time (alias of dateTaken)
digitizedTimeString?Date image was digitized
modifiedTimeString?Last modified date
Camera
cameraModelString?Camera/device model
cameraManufacturerString?Camera manufacturer
softwareString?Processing software
Capture Settings
flashString?Flash status
isoString?ISO sensitivity
apertureString?Aperture f-stop value
shutterSpeedString?Shutter speed (exposure time)
focalLengthString?Focal length in mm
whiteBalanceString?White balance setting
exposureBiasString?Exposure compensation
meteringModeString?Metering mode used
Image Properties
imageWidthInt?Original width in pixels
imageHeightInt?Original height in pixels
orientationString?Image rotation/orientation
colorSpaceString?Color space (sRGB, Adobe RGB, etc.)
thumbnailString?Base64 thumbnail (~5–20 KB). Avoid caching.
GPS is redacted by default. latitude, longitude and altitude are set to null unless you explicitly set redactGpsData = false in CameraCaptureConfig or GalleryConfig. Only disable redaction when the user has been clearly informed.

Extension Functions

Process PhotoResult and GalleryPhotoResult with built-in extensions for common operations.

Kotlin
ImagePickerLauncher(
    config = ImagePickerConfig(
        onPhotoCaptured = { result ->
            // For Compose UI
            val painter: Painter = result.loadPainter()

            // For file operations / upload
            val bytes: ByteArray = result.loadBytes()

            // For canvas / graphics
            val bitmap: ImageBitmap = result.loadImageBitmap()

            // For REST APIs
            val base64: String = result.loadBase64()

            // File info
            println("Name: ${result.fileName}")
            println("Size: ${result.fileSize} bytes")
            println("MIME: ${result.mimeType}")

            // Convert to KB
            val sizeKB = (result.fileSize ?: 0) / 1024.0
        }
    )
)

MIME Types

Use MimeType enum values to filter what files can be selected in GalleryPickerLauncher.

ValueMIME StringDescription
MimeType.IMAGE_ALLimage/*All image formats (default)
MimeType.IMAGE_JPEGimage/jpegJPEG images
MimeType.IMAGE_PNGimage/pngPNG images
MimeType.IMAGE_WEBPimage/webpWebP images
MimeType.IMAGE_GIFimage/gifGIF images
MimeType.IMAGE_BMPimage/bmpBMP images
MimeType.IMAGE_HEICimage/heicHEIC — iOS native format
MimeType.IMAGE_HEIFimage/heifHEIF — iOS native format
MimeType.APPLICATION_PDFapplication/pdfPDF documents

Utility Methods

Kotlin
// Convert to string list
val strings = MimeType.toMimeTypeStrings(
    MimeType.IMAGE_JPEG,
    MimeType.IMAGE_PNG
)
// → ["image/jpeg", "image/png"]

// Parse from string
val mt = MimeType.fromString("image/webp")
// → MimeType.IMAGE_WEBP

// Predefined groups
MimeType.COMMON_IMAGE_TYPES   // JPEG, PNG, GIF, WebP
MimeType.ALL_SUPPORTED_TYPES  // all enum entries
Android smart picker: images-only → native gallery, PDFs → file explorer, mixed → file explorer. Fully automatic, no config needed.

UI Customization

Customize the camera UI look, lifecycle callbacks, permission dialogs and confirmation screen.

UiConfig — Camera Visual Style

Kotlin
ImagePickerLauncher(
    config = ImagePickerConfig(
        onPhotoCaptured = { /* ... */ },
        cameraCaptureConfig = CameraCaptureConfig(
            captureButtonSize = 80.dp,
            uiConfig = UiConfig(
                buttonColor = Color(0xFF0EA5E9),   // capture button color
                iconColor = Color.White,          // flash / switch icons color
                buttonSize = 80.dp,
                flashIcon = Icons.Default.FlashOn,
                switchCameraIcon = Icons.Default.Cameraswitch
            )
        )
    )
)
ParameterTypeDescription
buttonColorColor?Capture button background color
iconColorColor?Icon color inside camera UI
buttonSizeDp?Capture button size
flashIconImageVector?Custom flash toggle icon
switchCameraIconImageVector?Custom camera switch icon
galleryIconImageVector?Custom gallery access icon

CameraCallbacks — Lifecycle Events

Kotlin
CameraCaptureConfig(
    cameraCallbacks = CameraCallbacks(
        onCameraReady = {
            // Camera preview is ready — hide loading indicator
            isLoading = false
        },
        onCameraSwitch = {
            // User switched between front/back camera
        },
        onPermissionError = { exception ->
            // Permission denied or unavailable
            showError(exception.message)
        },
        onGalleryOpened = {
            // User navigated to gallery from camera
        }
    )
)

CapturePhotoPreference — Quality Mode

ValueDescription
CapturePhotoPreference.FASTPrioritizes capture speed, lower quality
CapturePhotoPreference.BALANCEDDefault — balanced speed and quality
CapturePhotoPreference.QUALITYBest image quality, slower capture

PermissionAndConfirmationConfig — Permission Dialogs & Confirmation Screen

All permission and confirmation customization lives inside PermissionAndConfirmationConfig, nested in CameraCaptureConfig. The parameters below are all optional — omit any you don't need.

ParameterTypeDefaultDescription
skipConfirmationBooleanfalseSkip confirmation screen — deliver photo directly via onPhotoCaptured
customConfirmationView@Composable (PhotoResult, (PhotoResult)->Unit, ()->Unit) -> UnitnullReplace the built-in post-capture preview & confirm screen
customDeniedDialog@Composable ((onRetry: ()->Unit) -> Unit)nullDialog shown when camera permission is denied (can retry)
customSettingsDialog@Composable ((onOpenSettings: ()->Unit) -> Unit)nullDialog shown when permission is permanently denied (open settings)
cancelButtonTextIOSString?"Cancel"iOS only — label of the cancel button in the permission alert
onCancelPermissionConfigIOS(() -> Unit)?nulliOS only — callback when user taps cancel in permission alert
Deprecated / removed: customPickerDialog and directCameraLaunch do not exist in ImagePickerConfig — they are OCR-only parameters (ImagePickerOCRConfig). Do not use them with the standard picker.

1 — Skip confirmation (deliver photo immediately)

Kotlin
ImagePickerLauncher(
    config = ImagePickerConfig(
        onPhotoCaptured = { result -> // called immediately after capture },
        onError = { },
        cameraCaptureConfig = CameraCaptureConfig(
            permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
                skipConfirmation = true   // no review screen shown
            )
        )
    )
)

2 — Custom confirmation screen (Android)

Receives photoResult: PhotoResult, onConfirm: (PhotoResult) -> Unit and onRetry: () -> Unit. Use result.uri to display the preview.

Kotlin
CameraCaptureConfig(
    permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
        customConfirmationView = { photoResult, onConfirm, onRetry ->
            MyConfirmationView(
                result = photoResult,
                onConfirm = onConfirm,
                onRetry = onRetry
            )
        }
    )
)

// Example implementation:
@Composable
fun MyConfirmationView(
    result: PhotoResult,
    onConfirm: (PhotoResult) -> Unit,
    onRetry: () -> Unit
) {
    Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
        // Show preview using result.uri
        AsyncImage(
            model = result.uri,          // ✅ correct — use .uri not .image
            contentDescription = "Preview",
            modifier = Modifier.fillMaxWidth().weight(1f)
                .clip(RoundedCornerShape(16.dp)),
            contentScale = ContentScale.Crop
        )
        // Show file info using correct fields
        Text("Size: ${(result.fileSize ?: 0) / 1024} KB")  // ✅ fileSize in bytes
        Text("Format: ${result.mimeType ?: "unknown"}")         // ✅ mimeType (not .format)
        Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) {
            OutlinedButton(onClick = onRetry, modifier = Modifier.weight(1f)) {
                Text("Retry")
            }
            Button(onClick = { onConfirm(result) }, modifier = Modifier.weight(1f)) {
                Text("Use Photo")
            }
        }
    }
}

3 — Custom permission denied dialog

Shown when the user denies camera permission. Provides an onRetry lambda to re-launch the system permission prompt.

Kotlin
PermissionAndConfirmationConfig(
    customDeniedDialog = { onRetry ->
        Dialog(onDismissRequest = {}) {
            Card(shape = RoundedCornerShape(16.dp)) {
                Column(modifier = Modifier.padding(24.dp), horizontalAlignment = Alignment.CenterHorizontally) {
                    Text("Camera permission needed", fontWeight = FontWeight.Bold)
                    Spacer(Modifier.height(12.dp))
                    Text("We need camera access to take photos.", color = Color.Gray)
                    Spacer(Modifier.height(20.dp))
                    Button(onClick = onRetry, modifier = Modifier.fillMaxWidth()) {
                        Text("Grant Permission")
                    }
                }
            }
        }
    }
)

4 — Custom settings dialog (permanently denied)

Shown when the user has permanently denied the permission. Provides onOpenSettings to navigate to the system app settings.

Kotlin
PermissionAndConfirmationConfig(
    customSettingsDialog = { onOpenSettings ->
        Dialog(onDismissRequest = {}) {
            Card(shape = RoundedCornerShape(16.dp)) {
                Column(modifier = Modifier.padding(24.dp), horizontalAlignment = Alignment.CenterHorizontally) {
                    Text("Permission Permanently Denied", fontWeight = FontWeight.Bold)
                    Spacer(Modifier.height(12.dp))
                    Text("Go to Settings > Permissions > Camera to enable access.", color = Color.Gray)
                    Spacer(Modifier.height(20.dp))
                    Button(onClick = onOpenSettings, modifier = Modifier.fillMaxWidth()) {
                        Text("Open Settings")
                    }
                }
            }
        }
    }
)

5 — Full combined example

Kotlin
ImagePickerLauncher(
    config = ImagePickerConfig(
        onPhotoCaptured = { result ->
            // result.uri, result.fileSize (bytes), result.mimeType, result.width, result.height
        },
        onError = { exception -> showError(exception.message) },
        onDismiss = { navigateBack() },
        cameraCaptureConfig = CameraCaptureConfig(
            compressionLevel = CompressionLevel.HIGH,
            preference = CapturePhotoPreference.QUALITY,
            uiConfig = UiConfig(
                buttonColor = Color(0xFF0EA5E9),
                iconColor = Color.White
            ),
            cameraCallbacks = CameraCallbacks(
                onCameraReady = { isLoading = false },
                onPermissionError = { showPermissionBanner() }
            ),
            permissionAndConfirmationConfig = PermissionAndConfirmationConfig(
                skipConfirmation = false,
                customConfirmationView = { photoResult, onConfirm, onRetry ->
                    MyConfirmationView(photoResult, onConfirm, onRetry)
                },
                customDeniedDialog = { onRetry ->
                    MyDeniedDialog(onRetry = onRetry)
                },
                customSettingsDialog = { onOpenSettings ->
                    MySettingsDialog(onOpenSettings = onOpenSettings)
                }
            )
        ),
        enableCrop = false
    )
)
PhotoResult fields: use result.uri (not .image), result.fileSize in bytes (not KB), result.mimeType (not .format), result.width, result.height.

Gallery Options — GalleryConfig

Kotlin
GalleryPickerLauncher(
    config = GalleryConfig(
        allowMultiple = true,
        selectionLimit = 10,         // iOS only — max 10 items
        includeExif = true,
        redactGpsData = false,       // expose GPS (inform user!)
        mimeTypes = listOf(MimeType.IMAGE_JPEG, MimeType.IMAGE_PNG)
    ),
    onPhotosSelected = { photos -> /* ... */ },
    onError = { },
    onDismiss = { }
)
ParameterTypeDefaultDescription
allowMultipleBooleanfalseEnable multi-file selection
mimeTypesList<MimeType>[IMAGE_ALL]Allowed file types
selectionLimitInt30Max items when allowMultiple (iOS)
includeExifBooleanfalseExtract EXIF metadata
redactGpsDataBooleantrueStrip GPS from EXIF (privacy default)

React / Web Integration

ImagePickerKMP is available as an NPM package for JavaScript/TypeScript projects including React, Vue, Angular and Vanilla JS.

Installation

npm
npm install imagepickerkmp

React Component

TypeScript / React
import { useImagePicker } from 'imagepickerkmp';

function PhotoUploader() {
  const { openCamera, openGallery, result } = useImagePicker({
    onPhotoCaptured: (photo) => {
      console.log('Photo:', photo.fileName, photo.fileSize);
    },
    onError: (err) => console.error(err)
  });

  return (
    <div>
      <button onClick={openCamera}>Open Camera</button>
      <button onClick={openGallery}>Open Gallery</button>
      {result && <img src={result.uri} alt="captured" />}
    </div>
  );
}

WebRTC Camera

Browser camera via WebRTC. Works on mobile & desktop.

Drag & Drop

File picker with drag and drop support.

TypeScript

Full type definitions included out of the box.

Cross-Framework

React, Vue, Angular, Vanilla JS.

Full React guide available. See the React Integration Guide for complete examples with Next.js, Vite and plain React.

Cloud OCR Experimental

Extract text from images and PDFs using cloud AI providers. Supports Gemini, OpenAI, Claude, Azure, Ollama and any custom HTTP endpoint.

Gemini OCR

Kotlin
var isOCRActive by remember { mutableStateOf(false) }
var ocrResult by remember { mutableStateOf<OCRResult?>(null) }

@OptIn(ExperimentalOCRApi::class)
if (isOCRActive) {
    ImagePickerLauncherOCR(
        config = ImagePickerOCRConfig(
            scanMode = ScanMode.Cloud(
                provider = CloudOCRProvider.Gemini("YOUR_GEMINI_API_KEY")
            ),
            onOCRCompleted = { result ->
                ocrResult = result
                isOCRActive = false
            },
            onError = { isOCRActive = false },
            onCancel = { isOCRActive = false },
            allowedMimeTypes = listOf(MimeType.APPLICATION_PDF, MimeType.IMAGE_ALL),
            directCameraLaunch = false
        )
    )
}

Custom HTTP Endpoint

Kotlin
CloudOCRProvider.Custom(
    name = "My OCR Service",
    baseUrl = "https://api.mycompany.com/ocr/analyze",
    apiKey = "abc123",
    headers = mapOf(
        "X-API-Version" to "2.1",
        "X-Client-ID" to "mobile-app",
        "Authorization" to "Bearer $token"
    ),
    requestFormat = RequestFormat.MULTIPART_FORM,
    model = "enterprise-model-v3"
)
ProviderValue
Google GeminiCloudOCRProvider.Gemini("API_KEY")
OpenAICloudOCRProvider.OpenAI("API_KEY")
Anthropic ClaudeCloudOCRProvider.Claude("API_KEY")
Azure AICloudOCRProvider.Azure("ENDPOINT", "KEY")
Ollama (local)CloudOCRProvider.Ollama("http://localhost:11434")
CustomCloudOCRProvider.Custom(...)

PDF Support

Select PDF documents alongside images. On Android, PDFs automatically open the system file explorer.

Kotlin
GalleryPickerLauncher(
    allowMultiple = true,
    mimeTypes = listOf(
        MimeType.IMAGE_ALL,
        MimeType.APPLICATION_PDF
    ),
    onPhotosSelected = { files ->
        files.forEach { file ->
            when (file.mimeType) {
                "application/pdf" -> handlePDF(file)
                else -> handleImage(file)
            }
        }
    },
    onError = { },
    onDismiss = { showGallery = false }
)

Platform Support Matrix

FeatureAndroidiOSDesktopJS/WebWASM
Camera Capture
Gallery Picker
Crop UI
EXIF Metadata
Compression
OCR
PDF Support
Multiple Selection

API Reference

ImagePickerConfig

ParameterTypeDescription
onPhotoCaptured(PhotoResult) -> UnitCalled when photo is captured or selected
onError(Exception) -> UnitCalled on any error
onDismiss() -> UnitCalled when dismissed without selecting
cameraCaptureConfigCameraCaptureConfigCamera, compression, UI and crop config
enableCropBooleanShow crop UI after capture (default: false)

CameraCaptureConfig

ParameterTypeDefaultDescription
preferenceCapturePhotoPreferenceBALANCEDQuality/speed tradeoff
captureButtonSizeDp72.dpShutter button size
compressionLevelCompressionLevel?MEDIUMCompression — null = no compression
includeExifBooleanfalseExtract EXIF metadata
redactGpsDataBooleantrueStrip GPS from EXIF (privacy)
uiConfigUiConfigUiConfig()Camera UI visual customization
cameraCallbacksCameraCallbacksCameraCallbacks()Camera lifecycle callbacks
permissionAndConfirmationConfigPermissionAndConfirmationConfig…()Permission dialogs & confirmation screen
cropConfigCropConfigCropConfig()Crop UI configuration

GalleryConfig

ParameterTypeDefaultDescription
allowMultipleBooleanfalseEnable multi-file selection
mimeTypesList<MimeType>[IMAGE_ALL]Allowed MIME types
selectionLimitInt30Max items — iOS only
includeExifBooleanfalseExtract EXIF metadata
redactGpsDataBooleantrueStrip GPS from EXIF

CropConfig

ParameterTypeDefaultDescription
enabledBooleanfalseEnable crop UI after capture
aspectRatioLockedBooleanfalseLock aspect ratio during crop
circularCropBooleantrueShow circular crop option
squareCropBooleantrueShow square crop option
freeformCropBooleanfalseShow free-form crop option

PhotoResult / GalleryPhotoResult

GalleryPhotoResult is a typealias for PhotoResult — they are the same model.

FieldTypeDescription
uriString?Platform-native URI of the selected file
fileNameString?File name (reflects cropped name after crop)
fileSizeLong?Size in bytes since v1.0.35 — divide by 1024 for KB
mimeTypeString?MIME type string (e.g. "image/jpeg", "application/pdf")
widthInt?Image width in pixels
heightInt?Image height in pixels
exifExifData?EXIF metadata — populated when includeExif = true
Breaking change v1.0.35: fileSize returns bytes (previously KB). Migrate: val sizeKB = (result.fileSize ?: 0) / 1024.0

ExifData Fields

All fields are nullable. Availability depends on the device, image origin and whether redactGpsData is false.

FieldTypeDescription
GPS & Location
latitudeDouble?GPS latitude (stripped if redactGpsData = true)
longitudeDouble?GPS longitude
altitudeDouble?GPS altitude in metres
Date & Time
dateTimeString?Capture date/time (yyyy:MM:dd HH:mm:ss)
dateTimeOriginalString?Original capture date
dateTimeDigitizedString?Digitized date
Camera & Lens
makeString?Camera manufacturer
modelString?Camera model name
lensModelString?Lens model
focalLengthString?Focal length in mm
apertureString?Aperture (f-number)
Capture Settings
isoString?ISO sensitivity
shutterSpeedString?Shutter speed (APEX)
exposureTimeString?Exposure time in seconds
exposureBiasString?Exposure compensation (EV)
meteringModeString?Metering mode (multi, spot, etc.)
flashString?Flash fired / not fired
whiteBalanceString?White balance mode
Image Properties
imageWidthString?Image pixel width (EXIF tag)
imageHeightString?Image pixel height (EXIF tag)
orientationString?EXIF orientation (1–8)
colorSpaceString?Color space (sRGB, Adobe RGB, etc.)
softwareString?Software used to produce the image
thumbnailOffsetInt?Byte offset of embedded thumbnail
thumbnailLengthInt?Byte length of embedded thumbnail

Changelog

Recent releases and what changed in each version. Full history on GitHub Releases.

v1.0.35 Breaking New March 2026
  • Breaking: fileSize now returns bytes (was KB). Divide by 1024 to get KB.
  • New: Improved WASM target support with better browser compatibility.
  • New: MimeType.APPLICATION_PDF — select PDFs from gallery.
  • Fixed crop rotation on iOS in landscape orientation.
  • Detekt static analysis integrated into CI pipeline.
v1.0.34 New February 2026
  • New: Cloud OCR — Gemini, OpenAI, Claude, Azure, Ollama, Custom endpoint.
  • New: @ExperimentalOCRApi annotation for opt-in usage.
  • New: directCameraLaunch flag in OCR config.
  • Improved error handling with typed ImagePickerException.
v1.0.32 New Fix January 2026
  • New: Circular, square and freeform crop modes with zoom/rotation.
  • New: EXIF metadata extraction — GPS, ISO, exposure, camera model.
  • Fix: Memory leak on Android when cancelling gallery picker.
  • Kotlin 2.3.x required — ABI incompatible with older versions.
v1.0.28 New December 2025
  • New: Multiple gallery selection with allowMultiple = true.
  • New: MIME type filtering with mimeTypeMismatchMessage.
  • Android smart picker: images → gallery, PDFs → file explorer, mixed → file explorer.
View full changelog on GitHub