How to Upload an Image to Cloudinary using Expo Camera
The camera is a fundamental feature of modern mobile devices, enabling applications to capture images and record videos. The expo-camera library simplifies the integration of this functionality into React Native applications built with Expo.
This tutorial demonstrates how to use the Expo Camera API to take a picture and subsequently upload it to Cloudinary, a cloud-based image and video management service.
Prerequisites
To follow this tutorial, please ensure you are familiar with JavaScript/ES6 and meet the following requirements in your local development environment:
- Node.js version >= 14.x.x installed.
- Access to a package manager such as npm, yarn, or npx.
expo-cliinstalled globally, or use npx for local execution.
The source code for this tutorial is available at this Github repository.
Create an Expo App
Begin by creating a new Expo project and installing the expo-camera dependency. Execute the following commands in a terminal window:
npx create-expo-app project-name
# Select the blank template when prompted
cd project-name
npx expo install expo-camera
Create a Custom Camera Component
The expo-camera library provides a Camera React component that facilitates taking pictures and recording videos. It exposes numerous properties, including zoom, autofocus, white balance, and flash mode.
For this guide, we will create a component that renders the Camera in full-screen mode. Start by adding the following imports to your App.js file:
import React, { useState, useRef, useEffect } from 'react';
import {
StyleSheet,
Dimensions,
View,
Text,
TouchableOpacity
} from 'react-native';
import { Camera } from 'expo-camera';
import { AntDesign, MaterialIcons } from '@expo/vector-icons';
We'll use the Dimensions API from React Native to get the device's screen height, which will help in styling the camera view.
const WINDOW_HEIGHT = Dimensions.get('window').height;
const CAPTURE_SIZE = Math.floor(WINDOW_HEIGHT * 0.08);
To invoke methods on the Camera component, we need a reference to it using the useRef hook.
export default function App() {
const cameraRef = useRef();
// ... other state and functions
return (
<View style={styles.container}>
<Camera ref={cameraRef} style={styles.container} />
</View>
);
}
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject
}
});
Checking for Camera Permissions
To use the device's camera, your application must request permission from the user. We will handle this using a state variable and an effect.
- Define a state variable
hasPermissionusinguseState. - Create an asynchronous function
onHandlePermissionthat callsCamera.requestPermissionsAsync()and updates the state based on the result. - Invoke this function within a
useEffecthook when the component mounts.
export default function App() {
const cameraRef = useRef();
const [hasPermission, setHasPermission] = useState(null);
useEffect(() => {
onHandlePermission();
}, []);
const onHandlePermission = async () => {
const { status } = await Camera.requestCameraPermissionsAsync();
setHasPermission(status === 'granted');
};
if (hasPermission === null) {
return <View />;
}
if (hasPermission === false) {
return <Text style={styles.text}>No access to camera</Text>;
}
// ... rest of the component
}
The UI will display a loading state until the permission status is determined and a message if permission is denied.

Switching Between Camera Types
To allow switching between the front and back cameras, we will use another state variable, cameraType, and a function to toggle its value.
- Define
cameraTypewith a default value ofCamera.Constants.Type.back. - Define
isPreviewto track whether the app is showing the camera view or a captured image preview. - Create a
switchCamerafunction to toggle thecameraTypestate.
export default function App() {
// ... existing state and hooks
const [cameraType, setCameraType] = useState(Camera.Constants.Type.back);
const [isPreview, setIsPreview] = useState(false);
const switchCamera = () => {
if (isPreview) {
return;
}
setCameraType(prevCameraType =>
prevCameraType === Camera.Constants.Type.back
? Camera.Constants.Type.front
: Camera.Constants.Type.back
);
};
// ...
return (
<View style={styles.container}>
<Camera
ref={cameraRef}
style={styles.container}
type={cameraType}
// ... other props
/>
<View style={styles.container}>
{!isPreview && (
<View style={styles.bottomButtonsContainer}>
<TouchableOpacity disabled={!isCameraReady} onPress={switchCamera}>
<MaterialIcons name="flip-camera-ios" size={28} color="white" />
</TouchableOpacity>
{/* ... other buttons */}
</View>
)}
</View>
</View>
);
}
Taking and Previewing a Picture
The takePictureAsync() method from the Camera API captures an image and saves it to the app's cache. We will use the base64 option to get the image data directly.
- Create an
onSnapasync function that callscameraRef.current.takePictureAsync(). - If the picture is taken successfully, pause the camera preview and set
isPreviewtotrue.
const onSnap = async () => {
if (cameraRef.current) {
const options = { quality: 0.7, base64: true };
const data = await cameraRef.current.takePictureAsync(options);
const source = data.base64;
if (source) {
await cameraRef.current.pausePreview();
setIsPreview(true);
// We will handle the upload here later
}
}
};
Create a button to trigger onSnap and another to cancel the preview and return to the camera view.
const cancelPreview = async () => {
await cameraRef.current.resumePreview();
setIsPreview(false);
};
The UI should conditionally render the capture button or the preview-cancel button based on the isPreview state.

Setting up Cloudinary
To upload the image, you need a Cloudinary account. From your dashboard, you will need two pieces of information:
- Cloud Name: Found on your dashboard.
- Upload Preset: Create a new unsigned upload preset in Settings > Upload.

Uploading the Image
We will use the fetch API to send a POST request to the Cloudinary API endpoint. The body of the request will contain the base64 image data and your upload preset name.
Update the onSnap function to include the upload logic.
const onSnap = async () => {
if (cameraRef.current) {
const options = { quality: 0.7, base64: true };
const data = await cameraRef.current.takePictureAsync(options);
const source = data.base64;
if (source) {
await cameraRef.current.pausePreview();
setIsPreview(true);
let base64Img = `data:image/jpg;base64,${source}`;
let apiUrl = 'https://api.cloudinary.com/v1_1/YOUR_CLOUD_NAME/image/upload';
let data = {
"file": base64Img,
"upload_preset": "YOUR_UPLOAD_PRESET",
};
fetch(apiUrl, {
body: JSON.stringify(data),
headers: {
'content-type': 'application/json'
},
method: 'POST',
}).then(async r => {
let data = await r.json()
if(data.secure_url){
alert("Upload successful");
}
}).catch(err => alert("Cannot upload"));
}
}
};
Remember to replace YOUR_CLOUD_NAME and YOUR_UPLOAD_PRESET with your actual Cloudinary credentials.

Using Camera2 API for Android
To use Android's modern camera2 API for better performance and features, add the useCamera2Api prop to the Camera component.
<Camera
// ...
useCamera2Api={true}
/>
Conclusion
This tutorial covered the essentials of using the Expo Camera library to capture an image and upload it to a cloud service like Cloudinary. For saving images to the device's gallery, consider using the expo-media-library.