How to upload a file using Fetch

By

Learn how to upload a file to the server with the Fetch API, capturing it from a file input, appending it to a FormData object, and POSTing it to an endpoint.

~~~

To upload a file using Fetch, capture the file from a file input, append it to a FormData object, and pass that object as the body of a POST request. It’s a task that should be simple, but sometimes it leads to hours of research on the Web.

In this tutorial I explain you how to do it using fetch.

Given a form with a file input field:

<input type="file" id="fileUpload" />

We attach a change event handler on it:

document.querySelector('#fileUpload').addEventListener('change', event => {
  handleImageUpload(event)
})

and we manage the bulk of our logic in the handleImageUpload() function:

const handleImageUpload = event => {
  const files = event.target.files
  const formData = new FormData()
  formData.append('myFile', files[0])

  fetch('/saveImage', {
    method: 'POST',
    body: formData
  })
  .then(response => response.json())
  .then(data => {
    console.log(data)
  })
  .catch(error => {
    console.error(error)
  })
}

In this example we POST to the /saveImage endpoint.

We initialize a new FormData object and we assign it to the formData variable, and we append there the uploaded file. If we have more than one file input element, we’d have more than one append() call.

The data variable inside the second then() will contain the JSON parsed return data. I’m assuming your server will give you JSON as a response.

Don’t set the Content-Type header yourself

Here’s the mistake that costs people the most time with file uploads.

When the body is a FormData object, the browser sets the Content-Type header for you. It uses multipart/form-data, plus a boundary string that separates the fields in the request body.

If you set the header manually:

fetch('/saveImage', {
  method: 'POST',
  headers: { 'Content-Type': 'multipart/form-data' },
  body: formData
})

the boundary is missing, and the server can’t parse the request. The upload fails, often with a confusing error. The fix: leave the header out entirely.

Handling server errors

One thing to know about fetch: it does not reject when the server returns an error status like 500. The catch() only runs on network failures.

Check response.ok before parsing the response:

.then(response => {
  if (!response.ok) {
    throw new Error('Upload failed')
  }
  return response.json()
})

Now a failed upload ends up in the catch() too.

Uploading multiple files

If the input accepts multiple files:

<input type="file" id="fileUpload" multiple />

event.target.files contains all of them, and you append each one to the FormData object:

const handleImageUpload = event => {
  const formData = new FormData()

  for (const file of event.target.files) {
    formData.append('myFiles', file)
  }

  fetch('/saveImage', {
    method: 'POST',
    body: formData
  })
}

See how to handle images uploaded server-side

~~~

Related posts about js: