320

I am still trying to wrap my head around it.

I can have the user select the file (or even multiple) with the file input:

<form>
  <div>
    <label>Select file to upload</label>
    <input type="file">
  </div>
  <button type="submit">Convert</button>
</form>

And I can catch the submit event using <fill in your event handler here>. But once I do, how do I send the file using fetch?

fetch('/files', {
  method: 'post',
  // what goes here? What is the "body" for this? content-type header?
}).then(/* whatever */);
Mark Fisher
  • 965
  • 1
  • 11
  • 30
deitch
  • 14,019
  • 14
  • 68
  • 96
  • 2
    official document works for me after trying some answers failed: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Uploading_a_file , something can confirm: 1. need wrap file in FromData; 2. doesn't need to declare `Content-Type: multipart/form-data` in request header – Spark.Bao Aug 22 '18 at 14:09

11 Answers11

365

I've done it like this:

var input = document.querySelector('input[type="file"]')

var data = new FormData()
data.append('file', input.files[0])
data.append('user', 'hubot')

fetch('/avatars', {
  method: 'POST',
  body: data
})
Integ
  • 3,695
  • 2
  • 13
  • 7
  • 49
    You don't need to wrap the file contents in a `FormData` object if all you're uploading is the file (which is what the original question wants). `fetch` will accept `input.files[0]` above as its `body` parameter. – Klaus Sep 28 '17 at 09:03
  • 36
    If you have a PHP backend handling the file upload you will want to wrap the file in a FormData so that the $_FILES array is properly populated. – ddelrio1986 Oct 31 '17 at 14:43
  • 4
    I also noticed that Google Chrome would not show the file in the request payload without the FormData part for some reason. Seems like a bug in Google Chrome's Network panel. – ddelrio1986 Oct 31 '17 at 14:44
  • 8
    This should really be the correct answer. The other way works also but is more convoluted – jnmandal Dec 30 '17 at 20:01
  • what do you mean by /avatars? Are you referring to some backend API endpoint? – Kartikeya Mishra Jan 15 '18 at 08:06
  • 1
    how would you read this file from let's say Express backend. As the file is not sent as form data. It is instead sent as just the file object. Does express-fileupload or multer parse such payloads? – sakib11 Apr 22 '20 at 16:33
  • Just remember that you'll need a multi-part body parser to parse the formdata. A good module for this is "formidable": https://www.npmjs.com/package/formidable#readme – avisk Sep 07 '21 at 21:50
  • @sakib11 The file is sent within the form data, so you'll need a parser that parses multi-part bodies. So yes, Multer would work since it parses `multipart/form-data`, which is what the form data is. – avisk Sep 07 '21 at 21:55
  • Using `FormData` when uploading from browser to S3 with presigned URLs will make the file corrupted in S3. And tbh, any backend can handle file upload without `FormData`, including PHP. – Alisson Reinaldo Silva Mar 10 '22 at 19:23
  • I use FastApi with `def upload_file(file: UploadFile):` and it would not work without `FormData`. So thank you for your advice! – leosok Oct 20 '22 at 22:16
  • Best answer. How to get `user` at the backend? (eg. Django or PHP) – ar2015 Jan 24 '23 at 02:05
217

This is a basic example with comments. The upload function is what you are looking for:

// Select your input type file and store it in a variable
const input = document.getElementById('fileinput');

// This will upload the file after having read it
const upload = (file) => {
  fetch('http://www.example.net', { // Your POST endpoint
    method: 'POST',
    headers: {
      // Content-Type may need to be completely **omitted**
      // or you may need something
      "Content-Type": "You will perhaps need to define a content-type here"
    },
    body: file // This is your file object
  }).then(
    response => response.json() // if the response is a JSON object
  ).then(
    success => console.log(success) // Handle the success response object
  ).catch(
    error => console.log(error) // Handle the error response object
  );
};

// Event handler executed when a file is selected
const onSelectFile = () => upload(input.files[0]);

// Add a listener on your input
// It will be triggered when a file will be selected
input.addEventListener('change', onSelectFile, false);
Ross Rogers
  • 23,523
  • 27
  • 108
  • 164
Damien
  • 3,915
  • 1
  • 17
  • 18
  • 11
    Why does this example include Content-Type headers but another answer says to omit them when sending files with Fetch API? Which one is it? – jjrabbit Feb 24 '19 at 07:32
  • 66
    Do NOT set Content-Type. I spent a lot of time trying to make it work and then found this article saying to not set it. And it works! https://muffinman.io/uploading-files-using-fetch-multipart-form-data/ – Kostiantyn Apr 27 '19 at 02:05
  • 1
    how would you read this file from let's say Express backend. As the file is not sent as form data. It is instead sent as just the file object. Does express-fileupload or multer parse such payloads? – sakib11 Apr 22 '20 at 16:34
  • is fileinput the id of the button where you click to upload? – Angel Sep 02 '20 at 13:05
  • @sakib11 My node-express server was able to receive this (with the help of bodyParser.raw()) only with the `Content-Type` set to `application/x-www-form-urlencoded`. – charlieb Oct 25 '20 at 23:22
  • 1
    Just in case if someone needs to download file from one place and upload it to another: `const fileFetch = await fetch(fileDownloadUrl); const fileBlob = await fileFetch.blob(); upload(fileBlob)` – TitanFighter May 07 '21 at 15:43
  • 3
    Anyone else only getting an empty object on the server side? – avisk Sep 06 '21 at 21:40
171

An important note for sending Files with Fetch API

One needs to omit content-type header for the Fetch request. Then the browser will automatically add the Content type header including the Form Boundary which looks like

Content-Type: multipart/form-data; boundary=—-WebKitFormBoundaryfgtsKTYLsT7PNUVD

Form boundary is the delimiter for the form data

madhu131313
  • 7,003
  • 7
  • 40
  • 53
52

If you want multiple files, you can use this

var input = document.querySelector('input[type="file"]')

var data = new FormData()
for (const file of input.files) {
  data.append('files',file,file.name)
}

fetch('/avatars', {
  method: 'POST',
  body: data
})
Mark Amery
  • 143,130
  • 81
  • 406
  • 459
Alex Montoya
  • 4,697
  • 1
  • 30
  • 31
  • 8
    Note that explicitly passing `file.name` as the filename isn't necessary; per the [MDN docs on `FormData.append`](https://developer.mozilla.org/en-US/docs/Web/API/FormData/append), the default name for `File` objects already is the file's filename. – Mark Amery Jul 29 '20 at 22:12
31

To submit a single file, you can simply use the File object from the input's .files array directly as the value of body: in your fetch() initializer:

const myInput = document.getElementById('my-input');

// Later, perhaps in a form 'submit' handler or the input's 'change' handler:
fetch('https://example.com/some_endpoint', {
  method: 'POST',
  body: myInput.files[0],
});

This works because File inherits from Blob, and Blob is one of the permissible BodyInit types defined in the Fetch Standard.

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
  • This is the simplest answer but how does `body: myInput.files[0]` cause to the amount of bytes held in memory at the client side ? – bhantol Feb 07 '18 at 17:48
  • 2
    I would *expect* that with this solution the browser would be sensible enough to stream the file and not require it to be read into memory, @bhantol, but I haven't gone out of my way to find out (either empirically or by delving into the spec). If you wanted to confirm, you could try (in each of the major browsers) using this approach to upload a 50GB file or something, and see whether your browser tries to use too much memory and gets killed. – Mark Amery Feb 07 '18 at 17:52
  • Did not work for me. `express-fileupload` failed to parse the request stream. But `FormData` works like a charm. – attacomsian Aug 15 '19 at 12:18
  • 1
    @attacomsian At a glance, it looks to me like `express-fileupload` is a serverside library for handling `multipart/form-data` requests that contain files, so yeah, it's not compatible with this approach (which just directly sends the file as the request body). – Mark Amery Aug 15 '19 at 12:50
20

The accepted answer here is a bit dated. As of April 2020, a recommended approach seen on the MDN website suggests using FormData and also does not ask to set the content type. https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

I'm quoting the code snippet for convenience:

const formData = new FormData();
const fileField = document.querySelector('input[type="file"]');

formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);

fetch('https://example.com/profile/avatar', {
  method: 'PUT',
  body: formData
})
.then((response) => response.json())
.then((result) => {
  console.log('Success:', result);
})
.catch((error) => {
  console.error('Error:', error);
});
Raiyan
  • 1,589
  • 1
  • 14
  • 28
  • 10
    Using `FormData` will only work if the server is expecting form data. If the server wants a raw file as the body of the POST the accepted answer is correct. – Clyde May 14 '20 at 07:10
  • @Clyde what do you mean? I thought it was the client that decides what type of content to send to the server? – avisk Sep 06 '21 at 22:05
  • 1
    Yes, but the client and server have to agree on what content can be sent and how it is encoded. It is certainly possible to write server code to accept either a raw POST body or FormData (the headers will say what encoding has been used by the client) but often the server will be expecting a specific encoding so you have to send content that matches that. – Clyde Sep 06 '21 at 23:31
10

It would be nice to add php endpoint example. So that is js:

const uploadinput = document.querySelector('#uploadinputid');
const uploadBtn = document.querySelector('#uploadBtnid');
uploadBtn.addEventListener('click',uploadFile);

async function uploadFile(){
    const formData = new FormData();
    formData.append('nameusedinFormData',uploadinput.files[0]);    
    try{
        const response = await fetch('server.php',{
            method:'POST',
            body:formData
        } );
        const result = await response.json();
        console.log(result);
    }catch(e){
        console.log(e);

    }
}

That is php:

$file = $_FILES['nameusedinFormData'];
$temp = $file['tmp_name'];
$target_file = './targetfilename.jpg';
move_uploaded_file($_FILES["image"]["tmp_name"], $target_file);
Kordik
  • 129
  • 1
  • 3
5

Jumping off from Alex Montoya's approach for multiple file input elements

const inputFiles = document.querySelectorAll('input[type="file"]');
const formData = new FormData();

for (const file of inputFiles) {
    formData.append(file.name, file.files[0]);
}

fetch(url, {
    method: 'POST',
    body: formData })
Jerald Macachor
  • 131
  • 2
  • 4
3

The problem for me was that I was using a response.blob() to populate the form data. Apparently you can't do that at least with react native so I ended up using

data.append('fileData', {
  uri : pickerResponse.uri,
  type: pickerResponse.type,
  name: pickerResponse.fileName
 });

Fetch seems to recognize that format and send the file where the uri is pointing.

NickJ
  • 512
  • 4
  • 10
1

Here is my code:

html:

const upload = (file) => {
    console.log(file);

    

    fetch('http://localhost:8080/files/uploadFile', { 
    method: 'POST',
    // headers: {
    //   //"Content-Disposition": "attachment; name='file'; filename='xml2.txt'",
    //   "Content-Type": "multipart/form-data; boundary=BbC04y " //"multipart/mixed;boundary=gc0p4Jq0M2Yt08jU534c0p" //  ή // multipart/form-data 
    // },
    body: file // This is your file object
  }).then(
    response => response.json() // if the response is a JSON object
  ).then(
    success => console.log(success) // Handle the success response object
  ).catch(
    error => console.log(error) // Handle the error response object
  );

  //cvForm.submit();
};

const onSelectFile = () => upload(uploadCvInput.files[0]);

uploadCvInput.addEventListener('change', onSelectFile, false);
<form id="cv_form" style="display: none;"
          enctype="multipart/form-data">
          <input id="uploadCV" type="file" name="file"/>
          <button type="submit" id="upload_btn">upload</button>
</form>
<ul class="dropdown-menu">
<li class="nav-item"><a class="nav-link" href="#" id="upload">UPLOAD CV</a></li>
<li class="nav-item"><a class="nav-link" href="#" id="download">DOWNLOAD CV</a></li>
</ul>
  • 3
    From Review:  Hi, please don't answer just with source code. Try to provide a nice description about how your solution works. See: [How do I write a good answer?](https://stackoverflow.com/help/how-to-answer). Thanks – sɐunıɔןɐqɐp Dec 13 '19 at 10:27
1

How to upload a single file on select using HTML5 fetch

<label role="button">
  Upload a picture
  <input accept="image/*" type="file" hidden />
</label>
const input = document.querySelector(`input[type="file"]`);

function upload() {
  fetch(uplaodURL, { method: "PUT", body: input.files[0] });
}

input.addEventListener("change", upload); 
Konstantin Tarkus
  • 37,618
  • 14
  • 135
  • 121