How do I POST with multipart form data using fetch?
Asked Answered
U

3

193

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?

Unguarded answered 4/2, 2016 at 5:0 Comment(0)
T
345

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.

Teston answered 4/2, 2016 at 16:15 Comment(6)
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.Indohittite
What if I need an Authorization?Crumble
Just set the authorization header like any other fetch requestDejesus
Something like this will do it: { method: 'POST', headers: { 'Authorization': 'Bearer ' + token }, body: formData }Highflown
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.Keshiakesia
thank you for the last comment, I finally found my problem. This took me two hours to figure out, because I set the content-type header manually the boundary was completely wrong.Harville
W
39

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)
})
Whimsicality answered 21/11, 2016 at 6:41 Comment(7)
Note about the above method, DO NOT supply headers if you do it using FormData because it will override the boundary thats automatically set.Applewhite
Thanks @MattPengelly! How to set custom headers like Authorization then?Roband
@DragosStrugar you can still set headers (Authorization included), just don't manually set the Content-Type header if you are using FormData.Guaranty
DO NOT supply headers with 'Content-Type' if it's using FormData.Shirberg
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.Whimsicality
I think supplying formData directly to the body like this is a much more elegant approach if you don't need to send additional dataTheodore
@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.)Whimsicality
A
2
            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);
                })
        
    
Abscission answered 17/1, 2023 at 13:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.