173

I am fetching a URL like this:

fetch(url, {
  mode: 'no-cors',
  method: method || null,
  headers: {
    'Accept': 'application/json, application/xml, text/plain, text/html, *.*',
    'Content-Type': 'multipart/form-data'
  },
  body: JSON.stringify(data) || null,
}).then(function(response) {
  console.log(response.status)
  console.log("response");
  console.log(response)
})

My API expects the data to be of multipart/form-data so I am using content-type of this type... But it is giving me a response with status code 400.

What's wrong with my code?

VLAZ
  • 26,331
  • 9
  • 49
  • 67
varad
  • 7,309
  • 20
  • 60
  • 112

3 Answers3

317

You're setting the Content-Type to be multipart/form-data, but then using JSON.stringify on the body data, which returns application/json. You have a content type mismatch.

You will need to encode your data as multipart/form-data instead of json. Usually multipart/form-data is used when uploading files, and is a bit more complicated than application/x-www-form-urlencoded (which is the default for HTML forms).

The specification for multipart/form-data can be found in RFC 1867.

For a guide on how to submit that kind of data via javascript, see here.

The basic idea is to use the FormData object (not supported in IE < 10):

async function sendData(url, data) {
  const formData  = new FormData();
      
  for(const name in data) {
    formData.append(name, data[name]);
  }

  const response = await fetch(url, {
    method: 'POST',
    body: formData
  });

  // ...
}

Per this article make sure to NOT set the Content-Type header. The browser will set it for you, including the boundary parameter.

Ovidijus Parsiunas
  • 2,512
  • 2
  • 8
  • 18
rossipedia
  • 56,800
  • 10
  • 90
  • 93
  • 5
    const fd = new FormData(); // File to upload. fd.append('file', fileToUpload); fd.append('jsondatakey', 'jsondatavalue'); With this you will be able to send file along with some json data in body. – Jnana Mar 02 '20 at 11:45
  • 5
    What if I need an Authorization? – DAVE Dec 18 '20 at 11:22
  • Just set the authorization header like any other fetch request – Gustavo Santamaría Nov 11 '22 at 13:32
  • Something like this will do it: { method: 'POST', headers: { 'Authorization': 'Bearer ' + token }, body: formData } – Kaitn13 Nov 28 '22 at 11:50
  • Keep in mind that `name` in `formData.append(...)` call needs to be the name of the parameter that the Backend API is expecting. If it's not matching you will get **400 BadRequest** response. The second parameter can be a Blob as well or anything that derives from it as File. – meJustAndrew Feb 19 '23 at 00:58
37

I was recently working with IPFS and worked this out. A curl example for IPFS to upload a file looks like this:

curl -i -H "Content-Type: multipart/form-data; boundary=CUSTOM" -d $'--CUSTOM\r\nContent-Type: multipart/octet-stream\r\nContent-Disposition: file; filename="test"\r\n\r\nHello World!\n--CUSTOM--' "http://localhost:5001/api/v0/add"

The basic idea is that each part (split by string in boundary with --) has it's own headers (Content-Type in the second part, for example.) The FormData object manages all this for you, so it's a better way to accomplish our goals.

This translates to fetch API like this:

const formData = new FormData()
formData.append('blob', new Blob(['Hello World!\n']), 'test')

fetch('http://localhost:5001/api/v0/add', {
  method: 'POST',
  body: formData
})
.then(r => r.json())
.then(data => {
  console.log(data)
})
konsumer
  • 3,411
  • 1
  • 30
  • 31
  • 29
    Note about the above method, DO NOT supply headers if you do it using FormData because it will override the boundary thats automatically set. – Matt Pengelly Sep 06 '18 at 17:14
  • 2
    Thanks @MattPengelly! How to set custom headers like Authorization then? – Dragos Strugar Jan 06 '19 at 17:43
  • 12
    @DragosStrugar you can still set headers (Authorization included), just don't manually set the Content-Type header if you are using FormData. – RobertMcReed Mar 01 '19 at 04:28
  • 6
    DO NOT supply headers with 'Content-Type' if it's using FormData. – caot Jun 18 '19 at 16:30
  • 2
    In the curl example, you need it. In the `FormData` example you don't need it, because the browser sends that header for you & also manages all the mime-boundries, which is the point of this solution. – konsumer Jun 21 '19 at 20:02
  • I think supplying formData directly to the body like this is a much more elegant approach if you don't need to send additional data – Brian Peterson Jan 07 '20 at 21:05
  • 1
    @Brian Peterson: I agree. also it's not too bad even if you have other info to add: formData.set("field","value") for each other value (do it in a loop.) – konsumer Jan 09 '20 at 01:42
1
            let formData = new FormData();
            formData.append('profile-image', document.getElementById("uploadDP").value);
            fetch('http://15.207.55.233/user/helper/profile-image', {
                method: 'PATCH',
                headers: {
                    'Accept': 'application/json, application/xml, text/plain, text/html, *.*',
                    'Content-Type': 'multipart/form-data'
                },
                body: formData
            })
                .then(res => res.json())
                .then(res => {
                    console.log(res);                   
                })
                .catch(err => {
                    console.log(err);
                })
        
    
ixhimanshu
  • 77
  • 5