83

thanks for stopping by.

I want to send a new FormData() as the body of a POST request using the fetch api

the operation looks something like this

var formData = new FormData()
formData.append('myfile', file, 'someFileName.csv')

fetch('https://api.myapp.com', 
  {
    method: 'POST',
    headers: {
      "Content-Type": "multipart/form-data"
    },
    body: formData
  }
)

the problem here is that the boundary, something like

boundary=----WebKitFormBoundaryyEmKNDsBKjB7QEqu

never makes it into the Content-Type: header

it should look like this

Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryyEmKNDsBKjB7QEqu

when you try the "same" operation with a new XMLHttpRequest(), like so

var request = new XMLHttpRequest()
request.open("POST", "https://api.mything.com")
request.withCredentials = true
request.send(formData)

the headers are correctly set

Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryyEmKNDsBKjB7QEqu

so my question is,

  1. how do I make fetch behave exactly like XMLHttpRequest in this situation?

  2. if this is not possible, why?

Thanks everybody! This community is more or less the reason I have professional success.

VLAZ
  • 22,934
  • 9
  • 44
  • 60
James
  • 3,879
  • 2
  • 20
  • 26

6 Answers6

149

The solution to the problem is to explicitly set Content-Type to undefined so that your browser or whatever client you're using can set it and add that boundary value in there for you. Disappointing but true.

James
  • 3,879
  • 2
  • 20
  • 26
  • 22
    using fetch, I removed the Content-Type header and it worked. – sww314 Feb 28 '17 at 16:02
  • 20
    Unbelievable!! I spent hours until I found this!! `delete result.headers['Content-Type'];` worked for me, thanks!! – Crysfel Apr 12 '17 at 01:26
  • 1
    @Crysfel Thanks.. deleting content type worked for me :P – master_dodo Jun 09 '17 at 16:28
  • 9
    doesn't work for me. Setting to undefined defaults to text/plain. – sebnukem Jul 05 '17 at 01:51
  • @sebnukem which browser? I've never seen that happen, I'll admit though, I switched over to using Axios, it has a nicer api / behavior imo – James Jul 07 '17 at 05:41
  • @James No problem with the browser. I found out that the HTTP library I was using coerced the body payload to a string. – sebnukem Jul 07 '17 at 12:51
  • @sebnukem which http lib was that? this post is about `fetch` – James Jul 07 '17 at 17:31
  • @James, an in-house, proprietary lib – sebnukem Jul 07 '17 at 17:55
  • But why is it so? – Green Sep 08 '17 at 05:29
  • @Green Apparently you can define an arbitrary boundary -> https://stackoverflow.com/questions/3508338/what-is-the-boundary-in-multipart-form-data -- I just didn't, I only set the content type, but that is only half the job. On the other hand, If you just leave the content type alone, the browser can infer it, and set the type + the boundary to an appropriate compliant, random string for you. – James Sep 09 '17 at 18:08
  • 4
    Neither setting the content-type to undefined, nor deleting the content-type before making the POST with fetch did it for me... – Ahab Jan 16 '18 at 13:11
  • 2 years later, I'd recommend using something like axios unless you absolutely need isomorphic fetch - over the years I've found fetch to be more of a nuisance than a help – James Jan 16 '18 at 14:54
  • 1
    Setting value to undefined didn't work for me, but removing header worked :) – Oscar Calderon Mar 09 '18 at 15:13
  • explicitly setting `Content-Type` to `undefined` worked for me – jaeyow Mar 28 '18 at 05:38
  • 1
    Setting the Content-Type header to undefined seemed to come across for me as "Content-Type": undefined in the request itself. not defining it worked. – Scribblemacher Dec 23 '20 at 19:57
16

I removed "Content-Type" and added 'Accept' to http headers and it worked for me. Here are the headers I used,

'headers': new HttpHeaders({
        // 'Content-Type': undefined,
        'Accept': '*/*',
        'Authorization': 
        "Bearer "+(JSON.parse(sessionStorage.getItem('token')).token),
        'Access-Control-Allow-Origin': this.apiURL,
        'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, PATCH, DELETE',
        'Access-Control-Allow-Headers': 'origin,X-Requested-With,content-type,accept',
        'Access-Control-Allow-Credentials': 'true' 

      })
12
fetch(url,options)
  1. If you set a string as options.body, you have to set the Content-Type in request header ,or it will be text/plain by default.
  2. If options.body is specific object like let a = new FormData() or let b = new URLSearchParams(), you don't have to set the Content-Type by hand.It will be added automaticlly.

    • for a ,it will be something like

    multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

    as you see, the boundary is automaticlly added.

    • for b, it is application/x-www-form-urlencoded;
moonreader
  • 139
  • 1
  • 5
8

I had the same issue, and was able to fix it by excluding the Content-Type property, allowing the browser to detect and set the boundary and content type automatically.

Your code becomes:

var formData = new FormData()
formData.append('myfile', file, 'someFileName.csv')

fetch('https://api.myapp.com',
  {
    method: 'POST',
    body: formData
  }
)
Andrew Faulkner
  • 3,392
  • 3
  • 19
  • 23
5

Add headers:{content-type: undefined} browser will generate a boundary for you that is for uploading a file part-and-part with streaming if you are adding 'multiple/form-data' it means you should create streaming and upload your file part-and-part

So it is okay to add request.headers = {content-type: undefined}

Nver Abgaryan
  • 581
  • 7
  • 12
4

I'm using the aurelia-api (an wrapper to aurelia-fetch-client). In this case the Content-Type default is 'application/json'. So I set the Content-Type to undefined and it worked like a charm.

Diego Troitiño
  • 291
  • 2
  • 6