Skip to main content

expo-image-picker

Version: 55.0.6 Provides access to the system’s UI for selecting images and videos from the phone’s library or taking a photo with the camera. Supports image editing, multiple selection, and customizable media types.

Installation

npx expo install expo-image-picker

Usage

import * as ImagePicker from 'expo-image-picker';
import { Button, Image, View } from 'react-native';
import { useState } from 'react';

export default function App() {
  const [image, setImage] = useState<string | null>(null);

  const pickImage = async () => {
    const result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      allowsEditing: true,
      aspect: [4, 3],
      quality: 1,
    });

    if (!result.canceled) {
      setImage(result.assets[0].uri);
    }
  };

  return (
    <View>
      <Button title="Pick Image" onPress={pickImage} />
      {image && <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />}
    </View>
  );
}

API Reference

Methods

launchImageLibraryAsync(options)
(options?: ImagePickerOptions) => Promise<ImagePickerResult>
Opens the system UI for choosing an image or video from the library
const result = await ImagePicker.launchImageLibraryAsync({
  mediaTypes: ImagePicker.MediaTypeOptions.All,
  allowsEditing: true,
  aspect: [16, 9],
  quality: 0.8,
});
launchCameraAsync(options)
(options?: ImagePickerOptions) => Promise<ImagePickerResult>
Opens the system camera for taking a photo or video
const result = await ImagePicker.launchCameraAsync({
  mediaTypes: ImagePicker.MediaTypeOptions.Images,
  allowsEditing: true,
  quality: 1,
});
requestCameraPermissionsAsync()
() => Promise<CameraPermissionResponse>
Requests camera permissions from the user
const { status } = await ImagePicker.requestCameraPermissionsAsync();
if (status === 'granted') {
  // Permission granted
}
getCameraPermissionsAsync()
() => Promise<CameraPermissionResponse>
Checks current camera permission status
const { status } = await ImagePicker.getCameraPermissionsAsync();
requestMediaLibraryPermissionsAsync(writeOnly)
(writeOnly?: boolean) => Promise<MediaLibraryPermissionResponse>
Requests media library permissionsParameters:
  • writeOnly (boolean): Request write-only permissions. Defaults to false
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
getMediaLibraryPermissionsAsync(writeOnly)
(writeOnly?: boolean) => Promise<MediaLibraryPermissionResponse>
Checks current media library permission status
getPendingResultAsync()
() => Promise<ImagePickerResult | null>
Retrieves pending result on Android when MainActivity is destroyed
const result = await ImagePicker.getPendingResultAsync();
if (result) {
  // Handle recovered result
}

Hooks

useCameraPermissions(options)
() => [PermissionResponse | null, () => Promise<PermissionResponse>, () => Promise<PermissionResponse>]
Hook for camera permissions
const [permission, requestPermission] = ImagePicker.useCameraPermissions();

if (!permission?.granted) {
  await requestPermission();
}
useMediaLibraryPermissions(options)
(options?: { writeOnly?: boolean }) => [PermissionResponse | null, () => Promise<PermissionResponse>, () => Promise<PermissionResponse>]
Hook for media library permissions
const [permission, requestPermission] = ImagePicker.useMediaLibraryPermissions();

Types

ImagePickerOptions

mediaTypes
MediaTypeOptions
Type of media to select. Values: All, Images, Videos
allowsEditing
boolean
Whether to show editing UI after selection. Defaults to false
allowsMultipleSelection
boolean
Allow selecting multiple items. Defaults to false
aspect
[number, number]
Aspect ratio for editing (e.g., [4, 3])
quality
number
Image quality (0 to 1). Defaults to 0.2
videoMaxDuration
number
Maximum video duration in seconds
videoQuality
UIImagePickerControllerQualityType
Video quality: 0 (High), 1 (Medium), 2 (Low)
exif
boolean
Include EXIF data. Defaults to false
base64
boolean
Include base64 representation. Defaults to false

ImagePickerResult

type ImagePickerResult =
  | {
      canceled: false;
      assets: ImagePickerAsset[];
    }
  | {
      canceled: true;
      assets: null;
    };

ImagePickerAsset

uri
string
URI of the selected media
width
number
Width in pixels
height
number
Height in pixels
type
'image' | 'video'
Media type
fileName
string | null
Original file name
fileSize
number
File size in bytes
exif
object
EXIF metadata (if requested)
base64
string
Base64 data (if requested)
duration
number
Video duration in milliseconds

Enums

enum MediaTypeOptions {
  All = 'All',
  Images = 'Images',
  Videos = 'Videos',
}

Examples

Pick Image from Library

import * as ImagePicker from 'expo-image-picker';

async function selectImage() {
  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Images,
    quality: 1,
  });

  if (!result.canceled) {
    const asset = result.assets[0];
    console.log('Image URI:', asset.uri);
    console.log('Dimensions:', asset.width, 'x', asset.height);
  }
}

Take Photo with Camera

import * as ImagePicker from 'expo-image-picker';

async function takePhoto() {
  // Request permission first
  const { status } = await ImagePicker.requestCameraPermissionsAsync();
  if (status !== 'granted') {
    alert('Camera permission required');
    return;
  }

  const result = await ImagePicker.launchCameraAsync({
    allowsEditing: true,
    aspect: [1, 1],
    quality: 1,
  });

  if (!result.canceled) {
    console.log('Photo taken:', result.assets[0].uri);
  }
}

Select Multiple Images

import * as ImagePicker from 'expo-image-picker';

async function selectMultiple() {
  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Images,
    allowsMultipleSelection: true,
    quality: 0.8,
  });

  if (!result.canceled) {
    console.log(`Selected ${result.assets.length} images`);
    result.assets.forEach((asset) => {
      console.log(asset.uri);
    });
  }
}

Pick Video with Editing

import * as ImagePicker from 'expo-image-picker';

async function selectVideo() {
  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Videos,
    allowsEditing: true,
    videoMaxDuration: 60, // 60 seconds max
    videoQuality: ImagePicker.UIImagePickerControllerQualityType.High,
  });

  if (!result.canceled) {
    const video = result.assets[0];
    console.log('Video URI:', video.uri);
    console.log('Duration:', video.duration, 'ms');
  }
}

Get Image with EXIF Data

import * as ImagePicker from 'expo-image-picker';

async function getImageWithExif() {
  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Images,
    exif: true,
  });

  if (!result.canceled && result.assets[0].exif) {
    const exif = result.assets[0].exif;
    console.log('GPS:', exif.GPSLatitude, exif.GPSLongitude);
    console.log('Camera:', exif.Make, exif.Model);
    console.log('Date:', exif.DateTime);
  }
}

Complete Example with Permissions

import * as ImagePicker from 'expo-image-picker';
import { useState } from 'react';
import { View, Button, Image, StyleSheet, Text } from 'react-native';

export default function ImagePickerExample() {
  const [image, setImage] = useState<string | null>(null);
  const [cameraPermission, requestCameraPermission] =
    ImagePicker.useCameraPermissions();
  const [libraryPermission, requestLibraryPermission] =
    ImagePicker.useMediaLibraryPermissions();

  const takePhoto = async () => {
    if (!cameraPermission?.granted) {
      const result = await requestCameraPermission();
      if (!result.granted) return;
    }

    const result = await ImagePicker.launchCameraAsync({
      allowsEditing: true,
      aspect: [4, 3],
      quality: 1,
    });

    if (!result.canceled) {
      setImage(result.assets[0].uri);
    }
  };

  const pickImage = async () => {
    if (!libraryPermission?.granted) {
      const result = await requestLibraryPermission();
      if (!result.granted) return;
    }

    const result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      allowsEditing: true,
      aspect: [4, 3],
      quality: 1,
    });

    if (!result.canceled) {
      setImage(result.assets[0].uri);
    }
  };

  return (
    <View style={styles.container}>
      <Button title="Take Photo" onPress={takePhoto} />
      <Button title="Pick Image" onPress={pickImage} />
      
      {image && (
        <Image source={{ uri: image }} style={styles.image} />
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 20, gap: 10 },
  image: { width: 300, height: 300, marginTop: 20 },
});

Platform Support

PlatformSupported
iOS
Android
Web

Permissions

Camera and media library permissions are required.

iOS

Add to app.json:
{
  "expo": {
    "plugins": [
      [
        "expo-image-picker",
        {
          "photosPermission": "Allow $(PRODUCT_NAME) to access your photos.",
          "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera."
        }
      ]
    ]
  }
}

Android

Permissions are automatically added:
  • READ_EXTERNAL_STORAGE / READ_MEDIA_IMAGES / READ_MEDIA_VIDEO
  • WRITE_EXTERNAL_STORAGE (Android < 10)
  • CAMERA

Best Practices

  1. Always Request Permissions: Check and request permissions before launching picker
  2. Quality Settings: Use quality: 1 for profile photos, lower for thumbnails
  3. Handle Cancellation: Always check result.canceled before accessing assets
  4. Memory Management: Be cautious with base64 on large images
  5. Multiple Selection: Don’t use allowsEditing with allowsMultipleSelection
On Android, if the system kills the activity, use getPendingResultAsync() to recover the selection. Test by enabling “Don’t keep activities” in developer options.

Resources