A repository of bitesize articles, tips & tricks
(in both English and French) curated by Mirego’s team.

You should never set the “multipart/form-data” content type manually

Today (actually yesterday) I learned that when sending FormData with fetch, you should never set the Content-Type: multipart/form-data by yourself—you have to let the browser do it for you.

For example if we do this:

const body = new FormData();
body.append('foo', 'bar');

const method = 'POST';

const headers = {
  'Content-Type': 'multipart/form-data'
};

fetch('https://example.com/', {method, body, headers});

We’ll end up making this request (I stripped irrelevant headers):

> POST / HTTP/1.1
> Host: https://example.com
> Content-Type: multipart/form-data
> Content-Length: 175

-----------------------------109074902510780475434212898387
Content-Disposition: form-data; name="foo"

bar
-----------------------------109074902510780475434212898387--

Which won’t work. See the -----------------------------109074902510780475434212898387 separator that was added by the browser in our multipart body? It needs to be in the Content-Type as well.

But when we omit the Content-Type header in our request (and let the browser handle it for us):

const body = new FormData();
body.append('foo', 'bar');

const method = 'POST';

const headers = {};

fetch('https://example.com/', {method, body, headers});

We’ll end up making this request:

> POST / HTTP/1.1
> Host: https://example.com
> Content-Type: multipart/form-data; boundary=---------------------------109074902510780475434212898387
> Content-Length: 175

-----------------------------109074902510780475434212898387
Content-Disposition: form-data; name="foo"

bar
-----------------------------109074902510780475434212898387--

Which will now work because the ---------------------------109074902510780475434212898387 boundary set by the browser is present in both the body and the Content-Type header.

It turns out, boundaries are indeed important! 😅