350

My code:

fetch("api/xxx", {
    body: new FormData(document.getElementById("form")),
    headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        // "Content-Type": "multipart/form-data",
    },
    method: "post",
}

I tried to post my form using fetch api, and the body it sends is like:

-----------------------------114782935826962
Content-Disposition: form-data; name="email"

test@example.com
-----------------------------114782935826962
Content-Disposition: form-data; name="password"

pw
-----------------------------114782935826962--

(I don't know why the number in boundary is changed every time it sends...)

I would like it to send the data with "Content-Type": "application/x-www-form-urlencoded", what should I do? Or if I just have to deal with it, how do I decode the data in my controller?


To whom answer my question, I know I can do it with:

fetch("api/xxx", {
    body: "email=test@example.com&password=pw",
    headers: {
        "Content-Type": "application/x-www-form-urlencoded",
    },
    method: "post",
}

What I want is something like $("#form").serialize() in jQuery (w/o using jQuery) or the way to decode mulitpart/form-data in controller. Thanks for your answers though.

Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
Zack
  • 3,799
  • 2
  • 11
  • 12
  • What is issue with using `FormData`? – guest271314 Oct 09 '17 at 06:32
  • 1
    I want to post it as "email=test@example.com&password=pw". Is it possible? – Zack Oct 09 '17 at 06:41
  • 1
    *“I don't know why the number in boundary is changed every time it sends…”* – The boundary identifier is just a random identifier, it can be anything and does not have any meaning on its own. So there is nothing wrong with choosing a random number there (which is what clients usually do). – poke Oct 09 '17 at 09:22

11 Answers11

352

To quote MDN on FormData (emphasis mine):

The FormData interface provides a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using the XMLHttpRequest.send() method. It uses the same format a form would use if the encoding type were set to "multipart/form-data".

So when using FormData you are locking yourself into multipart/form-data. There is no way to send a FormData object as the body and not sending data in the multipart/form-data format.

If you want to send the data as application/x-www-form-urlencoded you will either have to specify the body as an URL-encoded string, or pass a URLSearchParams object. The latter unfortunately cannot be directly initialized from a form element. If you don’t want to iterate through your form elements yourself (which you could do using HTMLFormElement.elements), you could also create a URLSearchParams object from a FormData object:

const data = new URLSearchParams();
for (const pair of new FormData(formElement)) {
    data.append(pair[0], pair[1]);
}

fetch(url, {
    method: 'post',
    body: data,
})
.then(…);

Note that you do not need to specify a Content-Type header yourself.


As noted by monk-time in the comments, you can also create URLSearchParams and pass the FormData object directly, instead of appending the values in a loop:

const data = new URLSearchParams(new FormData(formElement));

This still has some experimental support in browsers though, so make sure to test this properly before you use it.

poke
  • 369,085
  • 72
  • 557
  • 602
  • 53
    You can also use an object or just `FormData` in the constructor directly instead of a loop: `new URLSearchParams(new FormData(formElement))` – monk-time Jan 19 '18 at 11:32
  • 1
    @monk-time At the time of writing that answer, the constructor argument to `URLSearchParams` was *very* new and had very limited support. – poke Jan 19 '18 at 11:38
  • 6
    sorry, that wasn't a complaint, just a note to everyone who will read this in the future. – monk-time Jan 19 '18 at 11:52
  • I just wasted a couple of hours pulling my hair out after I missed reading this line: `Note that you do not need to specify a Content-Type header yourself.` Leaving a note for people searching for this in future. If you are using flask, posting multipart FormData using fetch api and not receiving any data, remove your Content-Type header. – Prasanth Jul 30 '18 at 21:22
  • 1
    @Prasanth You may specify the content type yourself explicitly, but you have to pick the *correct* one. It’s easier to just leave it off and have `fetch` take care of it for you. – poke Jul 30 '18 at 21:29
  • what node library should I use for URLSearchParams? – chovy Mar 22 '20 at 20:25
  • 1
    @chovy `URLSearchParams` is built into most modern browsers as a global object and also works from Node. – poke Mar 23 '20 at 02:26
  • 3
    if you need to post FormData there is no need to use `URLSearchParams` fetch(url, { method: 'post', body: new FormData(form_element), }) – El' Sep 30 '20 at 11:56
  • @El Did you read the answer (and the question actually)? Sending `FormData` directly implies a specific content type. – poke Oct 01 '20 at 21:52
  • @shahrozbutt That assumes you are using jQuery which is not really common anymore today (and still wasn’t _that_ common back in 2017). The question actually specifically asks for a solution **without** using jQuery. – poke Jul 28 '21 at 13:38
  • 1
    isn't `multipart/form-data` better (more efficient) than `application/x-www-form-urlencoded`? I mean when would you want to use later over the former? – Muhammad Umer Feb 07 '22 at 00:40
  • Today I got the experience that I have to use 'Content-Type': 'application/x-www-form-urlencoded' together with URLSearchParams() otherwise it creates no FormData. – dec Feb 24 '22 at 10:05
  • I get a `PayloadTooLargeError` error – chovy Jun 26 '23 at 08:56
  • @chovy That’s the server refusing the request. – poke Jun 27 '23 at 10:37
212

Client

Do not set the content-type header.

// Build formData object.
let formData = new FormData();
formData.append('name', 'John');
formData.append('password', 'John123');

fetch("api/SampleData",
    {
        body: formData,
        method: "post"
    });

Server

Use the FromForm attribute to specify that binding source is form data.

[Route("api/[controller]")]
public class SampleDataController : Controller
{
    [HttpPost]
    public IActionResult Create([FromForm]UserDto dto)
    {
        return Ok();
    }
}

public class UserDto
{
    public string Name { get; set; }
    public string Password { get; set; }
}
regnauld
  • 4,046
  • 3
  • 23
  • 22
82

Use FormData and fetch to grab and send data

fetch(form.action, {method:'post', body: new FormData(form)});

function send(e,form) {
  fetch(form.action, {method:'post', body: new FormData(form)});

  console.log('We send post asynchronously (AJAX)');
  e.preventDefault();
}
<form method="POST" action="myapi/send" onsubmit="send(event,this)">
    <input hidden name="csrfToken" value="a1e24s1">
    <input name="email" value="a@b.com">
    <input name="phone" value="123-456-789">
    <input type="submit">    
</form>

Look on chrome console>network before/after 'submit'

For x-www-form-urlencoded use URLSearchParams

function send(e,form) {
  fetch(form.action, {method:'post', body: new URLSearchParams(new FormData(form))});

  console.log('We send post asynchronously (AJAX)');
  e.preventDefault();
}
<form method="POST" action="myapi/send" onsubmit="send(event,this)">
    <input hidden name="csrfToken" value="a1e24s1">
    <input name="email" value="a@b.com">
    <input name="phone" value="123-456-789">
    <input type="submit">    
</form>

Look on chrome console>network before/after 'submit'
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
  • 5
    Thank you very much, this is what I was looking for and it's amazing how easy pure JavaScript can be nowadays. Just look at that beautiful **1 liner** `fetch` code that `post` the `
    ` data, I'm still amazed how I found this. Bye bye jQuery.
    – haZh Oct 18 '20 at 16:08
  • Not important here at all but there's a typo in hidden input's name. For anyone who wonders why that input is there, `csrf` stands for Cross-site Request Forgery. – s3c Jul 21 '21 at 08:08
  • The option `method: 'post'` has no effect since the browser will use the method attribute of the `form` passed to `FormData`. Even when the method attribute is not defined in `form` the browser will fallback to default `GET` method. – Marco Mannes Nov 11 '21 at 19:36
  • @MarcoMannes if your remove `mehtod:'post'` from `fetch` params in snippet above, you will get `Request with GET/HEAD method cannot have body.` exception. If you remove `method="POST"` from html in above snippet, the `method:'post'` (in fetch params) will have effect - and browser will send POST - I check this by modify this snippet and using chrome>network tab (so actually we can remove this from html... but I will left it) – Kamil Kiełczewski Nov 11 '21 at 22:03
  • Is form.action the endpoint url? – Shivam Sahil Nov 13 '21 at 17:20
  • @ShivamSahil in this example yes – Kamil Kiełczewski Nov 13 '21 at 19:24
  • It's incomplete, You must then pass that `FormData` to the constructor of `URLSearchParams` to create the body, as written in the selected answer. – MortezaE Apr 16 '23 at 12:22
48

You can set body to an instance of URLSearchParams with query string passed as argument

fetch("/path/to/server", {
  method:"POST"
, body:new URLSearchParams("email=test@example.com&password=pw")
})

document.forms[0].onsubmit = async(e) => {
  e.preventDefault();
  const params = new URLSearchParams([...new FormData(e.target).entries()]);
  // fetch("/path/to/server", {method:"POST", body:params})
  const response = await new Response(params).text();
  console.log(response);
}
<form>
  <input name="email" value="test@example.com">
  <input name="password" value="pw">
  <input type="submit">
</form>
Community
  • 1
  • 1
guest271314
  • 1
  • 15
  • 104
  • 177
  • 2
    `Reflect.apply(params.set, params, props)` is a particularly unreadable way of saying `params.set(props[0], props[1])`. – poke Oct 09 '17 at 09:26
  • @poke `Reflect.apply(params.set, params, props)` is readable from perspective here. – guest271314 Oct 09 '17 at 13:30
  • This seems to be the only working answer here :/ thank you! :) – OZZIE Dec 18 '19 at 13:43
  • What if I send a **5MB image** file over *body:new URLSearchParams("img="+my5MBimage)* ? – PYK Oct 17 '20 at 17:09
  • 1
    @PYK In that case you can't use application/x-www-form-urlencoded but multipart/form-data: [application/x-www-form-urlencoded or multipart/form-data?](https://stackoverflow.com/questions/4007969/application-x-www-form-urlencoded-or-multipart-form-data) – mems Mar 24 '21 at 11:51
20

With fetch api it turned out that you do NOT have to include headers "Content-type": "multipart/form-data".

So the following works:

let formData = new FormData()
formData.append("nameField", fileToSend)

fetch(yourUrlToPost, {
   method: "POST",
   body: formData
})

Note that with axios I had to use the content-type.

nicolass
  • 526
  • 5
  • 8
9

"body:FormData" works but there're type complains, also "FormData" sets multipart headers. To make the things simplier, "body:URLSearchParams" with inline construction and headers set manually may be used :

function getAccessToken(code) {

    return fetch(tokenURL, 
        {
            method: 'POST',
            headers: {
               'Content-Type': 'application/x-www-form-urlencoded',                 
               'Accept': '*/*' 
            },            
            body: new URLSearchParams({
                'client_id':clientId,    
                'client_secret':clientSecret,
                'code':code,    
                'grant_type': grantType,
                'redirect_uri':'',
                'scope':scope
            })
        }
        )
    .then(
        r => return r.json()
    ).then(
        r => r.access_token
    )
  }
user16547619
  • 199
  • 1
  • 4
6

To add on the good answers above you can also avoid setting explicitly the action in HTML and use an event handler in javascript, using "this" as the form to create the "FormData" object

Html form :

<form id="mainForm" class="" novalidate>
<!--Whatever here...-->
</form>

In your JS :

$("#mainForm").submit(function( event ) {
  event.preventDefault();
  const formData = new URLSearchParams(new FormData(this));
  fetch("http://localhost:8080/your/server",
    {   method: 'POST',
        mode : 'same-origin',
        credentials: 'same-origin' ,
        body : formData
    })
    .then(function(response) {
      return response.text()
    }).then(function(text) {
        //text is the server's response
    });
});
magicsign
  • 97
  • 1
  • 6
6

‍These can help you:

let formData = new FormData();
            formData.append("name", "John");
            formData.append("password", "John123");
            fetch("https://yourwebhook", {
              method: "POST",
              mode: "no-cors",
              cache: "no-cache",
              credentials: "same-origin",
              headers: {
                "Content-Type": "form-data"
              },
              body: formData
            });
            //router.push("/registro-completado");
          } else {
            // doc.data() will be undefined in this case
            console.log("No such document!");
          }
        })
        .catch(function(error) {
          console.log("Error getting document:", error);
        });
Pablo Fernandez
  • 143
  • 3
  • 9
4

There are instructions on the MDN that the browser will automatically handle Content-Type:

A request will also automatically set a Content-Type header if none is set in the dictionary.

So we don't need to specify 'content-type' when we send a fetch request.

const formData = new FormData();
const fileField = document.querySelector('input[type="file"]');

formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);

fetch('https://example.com/profile/avatar', {
  method: 'PUT',
  body: formData
})
.then(response => response.json())
.then(result => {
  console.log('Success:', result);
})
.catch(error => {
  console.error('Error:', error);
});

If set content-type in headers. Browser will not try to split formdata in request payload.

I'm using fathcer to handle FormData, the same behavior as XHR.

import { formData } from '@fatcherjs/middleware-form-data';
import { json } from '@fatcherjs/middleware-json';
import { fatcher } from 'fatcher';

fatcher({
    url: '/bar/foo',
    middlewares: [json(), formData()],
    method: 'PUT',
    payload: {
        bar: 'foo',
        file: new File()
    },
    headers: {
        'Content-Type': 'multipart/form-data',
    },
})
    .then(res => {
        console.log(res);
    })
    .catch(err => {
        console.error(error);
    });
djvg
  • 11,722
  • 5
  • 72
  • 103
Fansy
  • 79
  • 3
1

@KamilKiełczewski answer is great if you are okay with the form data format being in form multipart style, however if you need the form submitted in query parameter styles:

You can also pass FormData directly to the URLSearchParams constructor if you want to generate query parameters in the way a would do if it were using simple GET submission.

        form = document.querySelector('form')
        const formData = new FormData(form);
        formData["foo"] = "bar";
        const payload = new URLSearchParams(formData)
        fetch(form.action, payload)
run_the_race
  • 1,344
  • 2
  • 36
  • 62
-1

With Content-Type: "mulitipart/form-data"

const formData = new FormData(document.getElementById("form"))

fetch("http://localhost:8000/auth/token", {
  method: "POST",
  body: formData,
  headers: {
    "Content-Type": "multipart/form-data"
  }
})

With Content-Type: "application/x-www-form-urlencoded"

const formData = new URLSearchParams(new FormData(document.getElementById("form")))

fetch("http://localhost:8000/auth/token", {
  method: "POST",
  body: formData,
  headers: {
    "Content-Type": "application/x-www-form-urlencoded"
  }
})
Abhishek
  • 726
  • 3
  • 10