A ten-field form, and the temptation to read every value one by one with a selector. The code works, then a field is renamed and nobody notices.
The browser already knows how to collect those values: that is what it does when a form is submitted without any JavaScript. A built-in object gives you access to that work.
Definition
FormData represents a set of name and value pairs, exactly the ones a form would send. Built from a form element, it reads every field at once. Built empty, it is filled by hand.
const form = document.querySelector("form");
const data = new FormData(form);
console.log(data.get("email")); // "camille@example.com"
console.log(data.getAll("interest")); // ["design", "code"]
data.append("source", "newsletter");
console.log(Object.fromEntries(data));A single name can carry several values, something no plain object can represent. get returns the first one, getAll returns them all, and Object.fromEntries keeps only the last.
The fields that are missing
The collection follows the rules of HTML, not those of your code. Four categories of field are left out without the slightest warning.
- No name attribute: a field without one does not exist as far as the form is concerned.
- Disabled: the
disabledattribute removes the field from the submission. - Unchecked box: nothing is sent, there is no false value.
- Submit button: its value only appears if you pass it as the second argument of the constructor.
That first line explains half of the empty submissions reported during development: the field is visible, filled in, and yet absent from the data.
Sending a form, files included
The object goes straight into the body of a fetch() call. It is the only simple way to transmit a file picked by a visitor.
form.addEventListener("submit", async (event) => {
event.preventDefault();
const response = await fetch("/api/signups", {
method: "POST",
body: new FormData(form),
});
console.log(response.ok);
});The call to preventDefault() stops the classic page reload, without which the browser would submit the form itself.
Never set the Content-Type header by hand for this kind of upload. The browser has to add a computed boundary that separates the fields; written manually, it is wrong and the server stops reading anything.
Frequently asked questions
How do I send the same data as JSON?
Convert the set into an object first, then serialize it: JSON.stringify(Object.fromEntries(data)). Watch out for multi-value fields, which collapse into a single value, and for files, which do not survive the conversion. For a URL query string, new URLSearchParams(data) does the job directly.
Can I read the file content before uploading?
Yes, the value attached to a file field is a File object, with its name, size and type. Its text() and arrayBuffer() methods return a promise on the content, which allows a preview or a size check before any network call.
Why does my field appear twice?
Because append adds a value without replacing the ones already there, unlike set, which overwrites everything under that name. A submission retried after a failure, on a reused object, therefore duplicates the values. Rebuild the object on each attempt rather than patching it.