Sending forms through JavaScript
When a user submits an HTML form, for example by clicking the submit button, the browser makes an HTTP request to send the data in the form. But instead of this declarative approach, web apps sometimes use JavaScript APIs such as fetch()
to send data programmatically to an endpoint that expects a form submission. This article explains why this is an important use case and how to do it.
Why use JavaScript to submit form data?
Standard HTML form submission, as described in our article on sending form data, loads the URL where the data was sent, which means the browser window navigates with a full page load.
However, many web apps, especially progressive web apps and single-page apps, use JavaScript APIs to request data from the server and update the relevant parts of the page, avoiding the overhead of a full page load.
For this reason, when these web apps want to submit form data, they use HTML forms only to collect input from the user, but not for data submission. When the user tries to send the data, the application takes control and sends the data using a JavaScript API such as fetch()
.
The problem with JavaScript form submission
If the server endpoint to which the web app sends the form data is under the web app developer's control, then they can send the form data in any way they choose: for example, as a JSON object.
However, if the server endpoint is expecting a form submission, the web app must encode the data in a particular way. For example, if the data is just textual, it is made of URL-encoded lists of key/value pairs and sent with a Content-Type
of application/x-www-form-urlencoded
. If the form includes binary data, it must be sent using the multipart/form-data
content type.
The FormData
interface takes care of the process of encoding data in this way, and in the rest of this article we'll provide a quick introduction to FormData
. For more details, see our guide to Using FormData objects.
Building a FormData
object manually
You can populate a FormData
object by calling the object's append()
method for each field you want to add, passing in the field's name and value. The value can be a string, for text fields, or a Blob
, for binary fields, including File
objects.
In the following example we send data as a form submission when the user clicks a button:
async function sendData(data) {
// Construct a FormData instance
const formData = new FormData();
// Add a text field
formData.append("name", "Pomegranate");
// Add a file
const selection = await window.showOpenFilePicker();
if (selection.length > 0) {
const file = await selection[0].getFile();
formData.append("file", file);
}
try {
const response = await fetch("https://example.org/post", {
method: "POST",
// Set the FormData instance as the request body
body: formData,
});
console.log(await response.json());
} catch (e) {
console.error(e);
}
}
const send = document.querySelector("#send");
send.addEventListener("click", sendData);
- We first construct a new, empty,
FormData
object. - Next, we call
append()
twice, to add two items to theFormData
object: a text field and a file. - Finally, we make a
POST
request using thefetch()
API, setting theFormData
object as the request body.
Note that we don't have to set the Content-Type
header: the correct header is automatically set when we pass a FormData
object into fetch()
.
Associating a FormData
object and a <form>
If the data you're submitting is really coming from a <form>
, you can populate the FormData
instance by passing the form into the FormData
constructor.
Suppose our HTML declares a <form>
element:
<form id="userinfo">
<div>
<label for="username">Enter your name:</label>
<input type="text" id="username" name="username" value="Dominic" />
</div>
<div>
<label for="avatar">Select an avatar</label>
<input type="file" id="avatar" name="avatar" required />
</div>
<input type="submit" value="Submit" />
</form>
The form includes a text input, a file input, and a submit button.
The JavaScript is as follows:
const form = document.querySelector("#userinfo");
async function sendData() {
// Associate the FormData object with the form element
const formData = new FormData(form);
try {
const response = await fetch("https://example.org/post", {
method: "POST",
// Set the FormData instance as the request body
body: formData,
});
console.log(await response.json());
} catch (e) {
console.error(e);
}
}
// Take over form submission
form.addEventListener("submit", (event) => {
event.preventDefault();
sendData();
});
We add a submit event handler for the form element. This first calls preventDefault()
to prevent the browser's built-in form submission, so we can take over. Then we call sendData()
, which retrieves the form element and passes it into the FormData
constructor.
After that, we send the FormData
instance as an HTTP POST
request, using fetch()
.
See also
Learning path
Advanced Topics
- Sending forms through JavaScript
- How to build custom form widgets
- HTML forms in legacy browsers
- Advanced styling for HTML forms
- Property compatibility table for form widgets