When it's time to add an image picker to your React Native app, you’ll quickly find yourself at a fork in the road. The decision usually comes down to two major players: the incredibly versatile react-native-image-picker and the wonderfully straightforward expo-image-picker.
Picking the right one isn't just a technicality; it's a strategic choice that will shape your development process, from setup all the way through long-term maintenance.
Choosing Your Image Picker Library
So, how do you decide? The answer almost always hinges on your project's foundation: are you building with a "bare" React Native setup or are you in the Expo ecosystem?
If you're in a bare React Native project, you have full control over the native side of things. This is where react-native-image-picker shines. It's built for developers who need—or want—to get their hands dirty with native modules to achieve maximum flexibility.
On the flip side, if you're using the Expo managed workflow, the choice is made for you. expo-image-picker is the integrated, go-to solution. It's designed to get you up and running fast by abstracting away the native configuration headaches that can often bog down a project.
Bare React Native vs. Expo Workflow
The react-native-image-picker library isn't just popular; it's a pillar of the bare React Native community. With over 8,000 stars on GitHub and a staggering 291,627 weekly downloads as of early 2026, its reputation is well-earned. One of its biggest draws is its ability to slash boilerplate code by up to 70% compared to building a custom solution from scratch.
This decision tree gives you a quick visual guide for the path you should take.

As the chart shows, the path is clear: Expo users should stick with expo-image-picker, while developers working in a bare environment will find react-native-image-picker to be the standard.
Choosing
react-native-image-pickermeans embracing native configuration for greater control, whileexpo-image-pickerprioritizes speed and simplicity by handling native details for you. Align this choice with your project’s long-term goals.
React Native Image Picker vs Expo ImagePicker at a Glance
To help you see the differences side-by-side, here’s a quick comparison. This table breaks down the key features and considerations for each library.
| Feature | react-native-image-picker | expo-image-picker |
|---|---|---|
| Best For | Bare React Native projects | Expo managed and bare workflow projects |
| Setup Complexity | Moderate (requires native configuration for iOS/Android) | Low (zero native config in managed workflow) |
| Customization | High (direct access to native options) | Moderate (provides a robust, but curated, set of options) |
| Maintenance | Higher (you manage native dependencies and upgrades) | Lower (managed by Expo SDK updates) |
| Video Support | Yes, with options for quality and duration limits | Yes, with similar options for quality and editing |
| Image Editing | Basic cropping (requires additional setup or libraries) | Built-in cropping, rotating, and other editing features |
| Offline Support | Excellent; works directly with the device file system | Excellent; functions entirely offline for media selection |
| Community Support | Massive community, widely used and documented | Strong support within the large and active Expo community |
Ultimately, neither library is "better" in a vacuum. The best choice depends entirely on your project's needs and your team's comfort level. If you're building a complex app that requires deep native integration, the control offered by react-native-image-picker is invaluable. But if your priority is speed and a simplified workflow, expo-image-picker is an absolute game-changer.
For a wider look at what tools can boost your workflow, check out our guide on the top React Native libraries and tools for efficient development.
Alright, you've picked your library. Now for the fun part: getting it into your project and making it work. This is where the documentation ends and the real-world setup begins. We'll walk through the process for both a standard React Native project and the much simpler Expo workflow.

Getting this initial installation right will save you a ton of debugging headaches down the road. Let’s start with the "bare" React Native path, where you have direct control over the native code.
Setup for a Bare React Native Project
If you're working in a bare React Native project, react-native-image-picker is almost certainly the library you'll be using. The first step is to get the package into your project with your package manager of choice.
npm install react-native-image-picker
…or if you're a Yarn person:yarn add react-native-image-picker
Since this library has native iOS components, there’s one more crucial step. You have to install the native dependencies, known as pods. Just pop into your ios directory and run this command:
npx pod-install
Don't skip this! It's the command that actually links the native code for the image picker in React Native so it can run on an iOS device. It’s an easy one to forget, and it's usually the first thing I check when something isn't working.
Handling Native Permissions
You can't just start accessing a user's photos or camera. You have to ask nicely. This is a non-negotiable rule on both the Apple App Store and Google Play Store. If you don't provide clear, honest reasons for needing access, your app will get rejected. It's as simple as that.
For iOS, you’ll need to open your Info.plist file (it lives in the ios/[YourProjectName] folder) and add a couple of keys with string descriptions.
- NSCameraUsageDescription: Explain why you need the camera. Something like, "We need camera access so you can take a new profile picture."
- NSPhotoLibraryUsageDescription: Explain why you need the photo library. For example, "We need access to your photos so you can choose one to upload."
For Android, the permission itself goes into your AndroidManifest.xml file, which you can find at android/app/src/main.
A Quick Word of Advice: Those descriptions you write in
Info.plistare what your users will see in the permission pop-up. Be direct and build trust. A vague or creepy-sounding message is a surefire way to get them to tap "Don't Allow."
The Easier Path: Expo Configuration
If you're building with Expo, you can breathe a sigh of relief. The whole process is much more straightforward because Expo handles most of the native configuration for you. Everything is managed through JavaScript/TypeScript and one simple config file. If you're new to this ecosystem, our comprehensive React Native Expo tutorial is a great place to start.
First, add the expo-image-picker package.
npx expo install expo-image-picker
It's really important to use expo install here. This command grabs the specific version of the library that's guaranteed to work with your project's Expo SDK version, which prevents a world of compatibility issues.
Next, you'll set up the same permission messages, but you’ll do it in your app.json or app.config.js file. Just add a plugins array to tell Expo what permissions your app needs.
{
"expo": {
"plugins": [
[
"expo-image-picker",
{
"photosPermission": "The app needs access to your photos to let you share them with your friends.",
"cameraPermission": "The app needs access to your camera to let you take a new profile picture."
}
]
]
}
}
And that’s it! When you build your app, Expo will automatically inject this information into the native Info.plist and AndroidManifest.xml files. You never have to touch Xcode or Android Studio.
Alright, with the setup and permissions out of the way, it’s time for the fun part: writing the code that actually lets users pick an image. This is where we’ll connect the dots and build the UI for selecting a photo from the gallery or taking a new one with the camera.
We'll be leaning on two main functions from react-native-image-picker: launchCamera and launchImageLibrary. Both are asynchronous, which means they play nicely with the modern async/await syntax, helping us keep our code clean and easy to read.
Launching the Camera for New Photos
Let's start with the camera. A classic use case is letting someone update their profile picture by snapping a new photo. With this library, you can fire up the device's native camera with a single function call.
The function takes an options object and a callback to handle whatever the user does. Inside the options, you can get specific about what you need.
import { launchCamera } from 'react-native-image-picker';
const openCamera = async () => {
const result = await launchCamera({
mediaType: 'photo',
quality: 0.7,
});
// We'll handle the result in a moment
};
Here, mediaType: 'photo' locks the camera to picture-taking mode, avoiding any video-related confusion. The quality option is a game-changer; it's a number from 0 to 1 that compresses the image. From my experience, a value of 0.7 hits the sweet spot, giving you a good balance between visual clarity and file size. This prevents you from trying to upload massive, multi-megabyte files.
A Quick Tip from the Trenches: Don't skip the
qualitysetting. Modern phone cameras produce huge images. Compressing them before they ever leave the user's device saves bandwidth, cuts down on your server costs, and makes your app feel significantly snappier during uploads.
Opening the Image Library
What about letting users pick a photo they’ve already taken? That’s where launchImageLibrary comes in. The beauty is that it works almost exactly like the camera function, giving you a consistent API to work with.
Here’s how you’d call it:
import { launchImageLibrary } from 'react-native-image-picker';
const chooseFromGallery = async () => {
const result = await launchImageLibrary({
mediaType: 'photo',
selectionLimit: 1, // Only allow one image to be selected
});
// We'll handle this result, too
};
Pay attention to selectionLimit: 1. By default, some devices might let users select multiple photos, which isn't what you want for something like a profile picture. Explicitly setting this to 1 ensures the user can only pick a single image, keeping the experience simple and predictable.
Handling the Picker Response and Displaying the Image
So, what happens after the user picks a photo or cancels? Both launchCamera and launchImageLibrary give you a response object that tells the whole story.
This response object contains a few key pieces of information:
didCancel: A boolean that’strueif the user simply closed the picker.errorCode: A string telling you what went wrong, like a'permission'error.assets: An array holding the good stuff—the media the user selected.
When a user successfully picks an image, the assets array will contain an object with all the image details, including its uri. This uri is a local file path on the device, which is exactly what you need to display the image in your app.
Let's pull this all together into a complete component. This TypeScript example uses React state to keep track of the selected image's URI and then renders it with the standard <Image> component.
import React, { useState } from 'react';
import { View, Button, Image, StyleSheet, Alert } from 'react-native';
import { launchCamera, launchImageLibrary, ImagePickerResponse, Asset } from 'react-native-image-picker';
const ImageSelector: React.FC = () => {
const [selectedImage, setSelectedImage] = useState<Asset | null>(null);
const handleResponse = (response: ImagePickerResponse) => {
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.errorCode) {
Alert.alert('Error', response.errorMessage);
} else if (response.assets && response.assets.length > 0) {
setSelectedImage(response.assets[0]);
}
};
const openCamera = () => {
launchCamera({ mediaType: 'photo' }, handleResponse);
};
const openGallery = () => {
launchImageLibrary({ mediaType: 'photo' }, handleResponse);
};
return (
{selectedImage?.uri && (
<Image source={{ uri: selectedImage.uri }} style={styles.image} />
)}
);
};
const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
padding: 20,
},
image: {
width: 200,
height: 200,
borderRadius: 100,
marginBottom: 20,
},
});
This simple pattern—calling the picker, handling its response, and updating state—is the fundamental workflow you'll use for just about any image picker in React Native.
It's also worth noting how much the Expo Image Picker has simplified things for developers in managed workflows. Its usage has jumped by 55% since Expo SDK 51 was released. In fact, in recent surveys of U.S. startups, 62% said they chose Expo specifically for its zero-config image handling, which can reduce integration time by up to 80% compared to a bare React Native setup. If you want to see a deep dive on how these tools are changing development, check out these trends and their impact on development workflows.
Implementing Advanced Image Features

While basic image selection gets the job done, modern apps often need more finesse. To create a truly polished user experience, you'll want to add features like image cropping and multi-selection. For that, we need to reach for a more specialized tool.
My go-to library for this is react-native-image-crop-picker. It's a powerhouse for handling complex image tasks right inside your React Native app, wrapping native iOS and Android libraries to give you the kind of features users have come to expect.
The numbers speak for themselves. This library pulls in around 150,000 weekly downloads on npm. A 2025 Stack Overflow survey even found that 52% of React Native developers who need an image picker choose one with cropping capabilities. Its popularity, which you can read more about on linuxfoundation.org, stems from its built-in editing tools that can reduce your reliance on other dependencies by as much as 60%.
Enforcing Aspect Ratios with the Cropper
One of the first advanced features I always implement is image cropping, especially for things like profile pictures. You almost always want avatars to be a perfect square. react-native-image-crop-picker makes this incredibly straightforward.
Instead of a generic launch function, you call a specific method like openPicker and pass in a few options.
import ImagePicker from 'react-native-image-crop-picker';
const selectProfilePicture = () => {
ImagePicker.openPicker({
width: 300,
height: 300,
cropping: true
}).then(image => {
console.log('Cropped image:', image);
});
};
When this function runs, the user picks an image, and the library immediately presents a native cropping UI. The magic happens with cropping: true combined with the width and height properties. This setup forces a square selection box, guaranteeing you get a perfectly uniform image every time.
Pro Tip: By setting
widthandheight, you aren't just resizing the final image. You're actually defining the aspect ratio of the cropping tool itself. This is the secret to getting consistent shapes for avatars, thumbnails, and banners.
Handling Multiple Image Selections
What about letting users pick more than one image at a time? This is essential for features like creating a photo gallery, adding multiple product shots, or uploading several images to a social media post. Making users select them one-by-one is a clunky experience nobody wants.
Luckily, this is another area where react-native-image-crop-picker shines. All it takes is a single option in the openPicker call to enable multi-select.
const selectMultipleImages = () => {
ImagePicker.openPicker({
multiple: true
}).then(images => {
// images is now an array of image objects
console.log('Selected images:', images);
});
};
With multiple: true, the picker presents a gallery where the user can tap and select as many photos as they need. After they hit "confirm," the promise resolves with an array of image objects, where each object has the same structure as a single image selection. From there, your app can loop through the array to display or upload the files.
If you need some ideas on how to show these images on the screen, check out our guide to using the React Native Image component.
Uploading Images and Handling Errors

Getting an image from the user's device is half the battle. Now you actually need to get that file onto your server. The standard way to do this is by sending it as multipart/form-data, a format specifically designed for bundling files with other data in a single request.
The secret sauce here is the FormData API, which is globally available in React Native, so there's nothing extra to install. You'll create a new FormData instance and then package the image data into a structure your backend can parse.
Constructing the Upload Request
Once you have the image asset from your image picker in React Native, you need to format it for the upload. The FormData object expects a specific shape for the file: an object containing the file's uri, name, and type.
const createFormData = (imageAsset) => {
const data = new FormData();
data.append('photo', {
uri: imageAsset.uri,
name: imageAsset.fileName,
type: imageAsset.type,
});
// You can append other data too
data.append('userId', '12345');
return data;
};
After building your FormData payload, you're ready to send it off with a fetch request. The most critical part of this step is setting the request headers correctly. You must include 'Content-Type': 'multipart/form-data'. If you forget this, your server won't know how to interpret the request body, and the upload will fail.
Robust Error Handling Strategies
It would be nice if every tap and upload worked perfectly, but in the real world, things break. Users cancel out of the image gallery, deny permissions, or have a spotty internet connection. A robust app anticipates these hiccups and guides the user through them instead of crashing.
The response from react-native-image-picker gives you everything you need. Look for flags like didCancel and the errorCode property to figure out what went wrong. Checking these before you do anything with the image asset is non-negotiable.
Proactively checking for cancellation or errors prevents your app from crashing. Always handle the
didCancelanderrorCodeflags from the picker's response before trying to access the image assets.
Here are the most common issues I've seen in production and how to handle them gracefully:
User Cancellation (
didCancelis true): This isn't really an error—the user just changed their mind. The best response is usually no response. Just close the modal or do nothing at all. Don't show an error message.Permission Denial (
errorCodeis 'permission'): The user hit "Don't Allow." This is your chance to explain why your app needs that permission. Show a friendly message explaining the feature and, if possible, provide a button that deep-links them to the app's settings.Network Failures (fetch
catchblock): The upload itself can fail for a million reasons. Always wrap yourfetchcall in atry...catchblock. If you land in thecatch, you can handle server errors, timeouts, or a lost connection. Inform the user the upload didn't work and give them a "Try Again" button.
Building in this level of resilience makes a huge difference. A simple Alert or a toast notification can turn a frustrating dead end into a smooth, professional user experience.
Common Questions About Image Pickers in React Native
Once you start working with an image picker in React Native, you'll quickly run into a few common roadblocks. Let's walk through some of the most frequent questions I see from developers and how to solve them.
How Do I Handle Permissions Gracefully?
Never just assume you have permission to a user's camera or photo library. The golden rule is to ask for permissions just-in-time, right before your app needs access. A great tool for this is react-native-permissions, which lets you check the status before you even ask.
What if the user says no? Don't just pop up the same system prompt again. A much better user experience is to show a friendly, custom message explaining why your app needs the permission. From there, you can add a button that deep-links them straight to your app’s settings page to enable it manually.
What Is the Best Way to Manage Image Size?
Huge, uncompressed images are a silent killer for app performance and can rack up server costs. Your first line of defense is the quality option built into the picker library. Simply setting it to 0.7 can dramatically reduce the file size by applying a reasonable level of compression right at the source.
For things like thumbnails or avatar previews, take it a step further with the maxWidth and maxHeight options. This resizes the image on the device before it gets sent anywhere, saving a ton of bandwidth and making your UI feel much snappier.
Can I Customize the Look of the Image Picker?
This is a common point of confusion. The short answer is, not really. The camera and photo gallery you see are native UI components managed directly by iOS or Android. Libraries like react-native-image-picker are essentially just bridges that let you call up these standard system interfaces.
Where you do have full control is in the UI you build around the experience:
- The design of the button that opens the camera or gallery.
- The layout you use to display the selected photos back in your app.
- Any cropping or editing tools you decide to offer after an image is picked.
How Do I Handle Both Images and Videos?
Most modern picker libraries can handle different media types out of the box. You'll typically find a mediaType option that lets you specify 'photo', 'video', or 'mixed'. When you get the response back from the picker, you just need to check the asset's type to know whether you should render an <Image> component or a video player.
At React Native Coders, we focus on practical tutorials and strategic insights to help you build exceptional mobile apps. Get more expert guides to stay ahead of the curve at https://reactnativecoders.com.





















Add Comment