42

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 6

95

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.

10
  • 1
    How to upload multiple picture at once?
    – Vinay
    Commented Sep 1, 2017 at 10:55
  • yes.. how to upload all photos at once. I mean by using promise.all() Commented May 20, 2018 at 11:49
  • 1
    Surely formData.append('photo2', { uri: localUri, name: filename, type }); would be better rather than multiple requests?
    – Rambatino
    Commented Jan 18, 2020 at 13:00
  • 1
    This 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
  • 1
    How did it work? Local URI shouldn't be accessible on the backend . right?
    – Furquan
    Commented Jul 30, 2020 at 13:53
11

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']);
?>
4
  • 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
  • 1
    Even 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
6
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,
  }
});
6

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

1
  • I was able to upload small images before, this solved uploading large ones too in expo, thanks.
    – snubbus
    Commented Mar 11, 2023 at 21:50
3

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
          ...

2
  • 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
0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.