I'm making an app with Expo and want to let the user take a photo or pick one from their camera roll and upload it to my server. How do I do this?
6 Answers
Use the Expo ImagePicker
API to display either the camera or the camera roll and get back information about the selected image:
async function takeAndUploadPhotoAsync() {
// Display the camera to the user and wait for them to take a photo or to cancel
// the action
let result = await ImagePicker.launchCameraAsync({
allowsEditing: true,
aspect: [4, 3],
});
if (result.cancelled) {
return;
}
// ImagePicker saves the taken photo to disk and returns a local URI to it
let localUri = result.uri;
let filename = localUri.split('/').pop();
// Infer the type of the image
let match = /\.(\w+)$/.exec(filename);
let type = match ? `image/${match[1]}` : `image`;
// Upload the image using the fetch and FormData APIs
let formData = new FormData();
// Assume "photo" is the name of the form field the server expects
formData.append('photo', { uri: localUri, name: filename, type });
return await fetch(YOUR_SERVER_URL, {
method: 'POST',
body: formData,
headers: {
'content-type': 'multipart/form-data',
},
});
}
For a more comprehensive example including the server code, see this repo: https://github.com/expo/image-upload-example.
-
1
-
yes.. how to upload all photos at once. I mean by using promise.all() Commented May 20, 2018 at 11:49
-
1Surely
formData.append('photo2', { uri: localUri, name: filename, type });
would be better rather than multiple requests? Commented Jan 18, 2020 at 13:00 -
1This does not work for me, compare to postman fetch generated code, your formData use only two params, while postman use 3. can you please explain the difference and why postman can do and this code can't do for me? Commented Mar 30, 2020 at 12:19
-
1How did it work? Local URI shouldn't be accessible on the backend . right?– FurquanCommented Jul 30, 2020 at 13:53
The official examples use Node.js, here's how with PHP:
Expo
async function takePhotoAndUpload() {
let result = await ImagePicker.launchCameraAsync({
allowsEditing: false, // higher res on iOS
aspect: [4, 3],
});
if (result.cancelled) {
return;
}
let localUri = result.uri;
let filename = localUri.split('/').pop();
let match = /\.(\w+)$/.exec(filename);
let type = match ? `image/${match[1]}` : `image`;
let formData = new FormData();
formData.append('photo', { uri: localUri, name: filename, type });
return await fetch('http://example.com/upload.php', {
method: 'POST',
body: formData,
header: {
'content-type': 'multipart/form-data',
},
});
}
upload.php
<?php
move_uploaded_file($_FILES['photo']['tmp_name'], './photos/' . $_FILES['photo']['name']);
?>
-
I want to save image name to the database and actual photo to the server; how can I do it? Commented Jul 1, 2019 at 5:12
-
Post both to the server, save the image on hard drive and save file name in database Commented Mar 30, 2020 at 12:19
-
1Even though I find this solution almost everywhere, it didn't work for me using Django Rest Framework as backend. However, passing a base64 encoded version of the blob works like a charm. Commented Jun 8, 2020 at 11:39
-
this isn't working on server side dumping $_FILES gives this : array(1) { ["photo"]=> array(5) { ["name"]=> string(40) "0125ff6e-b04e-4d76-b8d4-a3c411952f29.jpg" ["type"]=> string(0) "" ["tmp_name"]=> string(0) "" ["error"]=> int(1) ["size"]=> int(0) } } Commented Mar 27, 2021 at 16:04
import React, { Component } from 'react';
import {
ActivityIndicator,
Button,
Clipboard,
Image,
Share,
StatusBar,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { Constants } from 'expo';
import * as Permissions from 'expo-permissions';
import * as ImagePicker from 'expo-image-picker';
export default class App extends Component {
state = {
image: null,
uploading: false,
};
render() {
let {
image
} = this.state;
return (
<View style={styles.container}>
<StatusBar barStyle="default" />
<Text
style={styles.exampleText}>
Example: Upload ImagePicker result
</Text>
<Button
onPress={this._pickImage}
title="Pick an image from camera roll"
/>
<Button onPress={this._takePhoto} title="Take a photo" />
{this._maybeRenderImage()}
{this._maybeRenderUploadingOverlay()}
</View>
);
}
_maybeRenderUploadingOverlay = () => {
if (this.state.uploading) {
return (
<View
style={[StyleSheet.absoluteFill, styles.maybeRenderUploading]}>
<ActivityIndicator color="#fff" size="large" />
</View>
);
}
};
_maybeRenderImage = () => {
let {
image
} = this.state;
if (!image) {
return;
}
return (
<View
style={styles.maybeRenderContainer}>
<View
style={styles.maybeRenderImageContainer}>
<Image source={{ uri: image }} style={styles.maybeRenderImage} />
</View>
<Text
onPress={this._copyToClipboard}
onLongPress={this._share}
style={styles.maybeRenderImageText}>
{image}
</Text>
</View>
);
};
_share = () => {
Share.share({
message: this.state.image,
title: 'Check out this photo',
url: this.state.image,
});
};
_copyToClipboard = () => {
Clipboard.setString(this.state.image);
alert('Copied image URL to clipboard');
};
_takePhoto = async () => {
const {
status: cameraPerm
} = await Permissions.askAsync(Permissions.CAMERA);
const {
status: cameraRollPerm
} = await Permissions.askAsync(Permissions.CAMERA_ROLL);
// only if user allows permission to camera AND camera roll
if (cameraPerm === 'granted' && cameraRollPerm === 'granted') {
let pickerResult = await ImagePicker.launchCameraAsync({
allowsEditing: true,
aspect: [4, 3],
});
if (!pickerResult.cancelled) {
this.setState({ image: pickerResult.uri });
}
this.uploadImageAsync(pickerResult.uri);
}
};
_pickImage = async () => {
const {
status: cameraRollPerm
} = await Permissions.askAsync(Permissions.CAMERA_ROLL);
// only if user allows permission to camera roll
if (cameraRollPerm === 'granted') {
let pickerResult = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
base64: true,
aspect: [4, 3],
});
if (!pickerResult.cancelled) {
this.setState({ image: pickerResult.uri});
}
this.uploadImageAsync(pickerResult.uri);
}
};
uploadImageAsync(pictureuri) {
let apiUrl = 'http://123.123.123.123/ABC';
var data = new FormData();
data.append('file', {
uri: pictureuri,
name: 'file',
type: 'image/jpg'
})
fetch(apiUrl, {
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data'
},
method: 'POST',
body: data
}).then(
response => {
console.log('succ ')
console.log(response)
}
).catch(err => {
console.log('err ')
console.log(err)
} )
}
}
const styles = StyleSheet.create({
container: {
alignItems: 'center',
flex: 1,
justifyContent: 'center',
},
exampleText: {
fontSize: 20,
marginBottom: 20,
marginHorizontal: 15,
textAlign: 'center',
},
maybeRenderUploading: {
alignItems: 'center',
backgroundColor: 'rgba(0,0,0,0.4)',
justifyContent: 'center',
},
maybeRenderContainer: {
borderRadius: 3,
elevation: 2,
marginTop: 30,
shadowColor: 'rgba(0,0,0,1)',
shadowOpacity: 0.2,
shadowOffset: {
height: 4,
width: 4,
},
shadowRadius: 5,
width: 250,
},
maybeRenderImageContainer: {
borderTopLeftRadius: 3,
borderTopRightRadius: 3,
overflow: 'hidden',
},
maybeRenderImage: {
height: 250,
width: 250,
},
maybeRenderImageText: {
paddingHorizontal: 10,
paddingVertical: 10,
}
});
For Expo, nothing worked for me except using the Expo FileSystem uploadAsync
uploadImage = async ({ imageUri } }) => FileSystem.uploadAsync(
apiUrl,
imageUri,
{
headers: {
// Auth etc
},
uploadType: FileSystem.FileSystemUploadType.MULTIPART,
fieldName: 'files',
mimeType: 'image/png',
});
Note - imageUri in format of file:///mypath/to/image.png
-
I was able to upload small images before, this solved uploading large ones too in expo, thanks.– snubbusCommented Mar 11, 2023 at 21:50
Since the chosen solution didn't actually work for me, here's how I made file uploads work with Expo and Django Rest Framework as backend.
const blobToBase64 = blob => {
const reader = new FileReader();
reader.readAsDataURL(blob);
return new Promise(resolve => {
reader.onloadend = () => {
resolve(reader.result);
};
});
};
const formData = new FormData();
const base64 = await blobToBase64(blob);
formData.append('file', base64);
formData.append('data', JSON.stringify(payload)); // additional data (I parse the string as json in the backend to get my payload back)
// same code as chosen answer, this was not part of the problem
return await fetch(YOUR_SERVER_URL, {
method: 'POST',
body: formData,
headers: {
'content-type': 'multipart/form-data',
},
});
In Django, I can use a custom parser to decode the base64 string to bytes and then create a SimpleUploadedFile
object with it.
class MultipartJsonParser(parsers.MultiPartParser):
def parse(self, stream, media_type=None, parser_context=None):
result = super().parse(
stream,
media_type=media_type,
parser_context=parser_context
)
base64_file = result.data.get('file')
file_parts = base64_file.split(',')
mime_type = re.sub(r'^data:([\w\/]+);base64$', '\\1', file_parts[0])
file = SimpleUploadedFile('file', base64.b64decode(file_parts[1]), mime_type)
data = json.loads(result.data["data"]) or {} # additional data sent by Expo app
qdict = QueryDict('', mutable=True)
qdict.update(data)
return parsers.DataAndFiles(qdict, {'file': file})
class MyUploadView(ModelViewSet):
parser_classes = (MultipartJsonParser, parsers.JSONParser)
def create(self, request, *args, **kwargs):
# request.data should have a 'file' property with a SimpleUploadedFile object
...
-
Had to remove conten-type header for Rails 5.2 to work Commented Feb 11, 2022 at 16:56
-
In your answer, you actually have some image data being uploaded as some JSON encoded in base 64. None of the other answers have this, so I struggle to understand exactly how it is they are supposed to work. Commented Dec 12, 2023 at 15:52
For to who's not find or solved this issue. I spend three days to find solution and I got it. In my case it was a naming elements of data object. Android version 10, expo 4.11.0,
this is front
async function uploadImage(uploadFile){
const data = new FormData()
data.append('name',{
name: image_name, {/* name your image whatever you want*/}
type: 'image/jpeg', {/* type of image that you're uploading*/}
uri: uploadFile {/*data, file or image from ImagePicker here you should pass uri data but not all data from ImagePicker*/}
})
{/*names of data object should be like this: name, type, uri*/}
const response = await fetch(my_upload_api.php,{
method: 'POST',
body: data,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
this is backend on PHP
if(!empty($_FILES['name']['name'])){
$target_dir = 'my folder where I put all images';
if(!file_exists($target_dir)){
$data = array(
(object)array(
'code' => '400',
'message' => 'Can\'t fined folder.'
)
);
$json = json_encode($data);
echo $json;
die();
}
$target_file = $target_dir . basename($_FILES['name']['name']);
$image_file_type = pathinfo($target_file,PATHINFO_EXTENSION);
if(file_exists($target_file)){
$data = array(
(object)array(
'code' => '400',
'message' => 'Sorry. File already exists.'
)
);
$json = json_encode($data);
echo $json;
die();
}
if($_FILES['name']['size'] > 50000000){
$data = array(
(object)array(
'code' => '400',
'message' => 'Sorry. Your file is too large.'
)
);
$json = json_encode($data);
echo $json;
die();
}
if(move_uploaded_file($_FILES['name']['tmp_name'], $target_file)){
$data = array(
(object)array(
'code' => '200',
'message' => 'Successfuly your file has been uploaded.',
'name' => $_FILES['name']
)
);
$json = json_encode($data);
echo $json;
die();
}else{
$data = array(
(object)array(
'code' => '400',
'message' => 'Sorry. There was something wrong. Try it again.'
)
);
$json = json_encode($data);
echo $json;
die();
}
}
It was my first blog where I was trying to find solution. If solution was here I perhaps spend one or less days to solve this issue. I hope I can help someone.