JavaScript Form Handling and Submission Tutorial
Learn how to handle form submissions in JavaScript using the submit event, read input values, validate data, and use FormData to send information without reloading the page.
When a user fills out a form and presses the submit button (or hits Enter in a text field), the browser fires a submit event on the <form> element. By default, the browser then sends the form data to the URL in the form's action attribute and reloads the page.
JavaScript lets you intercept that process. You listen for the submit event, read the values the user entered, validate them, and decide what happens next: send the data via fetch, show a confirmation message, or stop the submission if something is wrong.
Here is the smallest working example:
const form = document.querySelector("form");
form.addEventListener("submit", (event) => {
event.preventDefault();
console.log("Form submitted, but the page did not reload.");
});The listener is on the <form>, not the button. That matters because a form can be submitted in multiple ways: clicking a submit button, pressing Enter in a text input, or calling form.requestSubmit() programmatically. The submit event on the form catches all of them.
How the Submit Event Works
The browser runs HTML constraint validation first. If a required field is empty or a pattern does not match, the invalid event fires on that input, and the submit event never runs. If validation passes, submit fires and your JavaScript gets control.
This means HTML validation attributes like required, minlength, and pattern are your first line of defense. They block invalid submissions before your JavaScript even runs.
Reading Form Values with FormData
The FormData API gives you all the form's named fields and their values in one object. Pass the form element to the constructor, then read individual fields by the name attribute used in the HTML:
form.addEventListener("submit", (event) => {
event.preventDefault();
const formData = new FormData(form);
console.log(formData.get("email")); // value of <input name="email">
console.log(formData.get("password")); // value of <input name="password">
});When you need every field instead of specific ones, entries() gives you an iterator of [name, value] pairs you can loop over directly:
for (const [name, value] of formData.entries()) {
console.log(`${name}: ${value}`);
}| Method | What it does |
|---|---|
formData.get(name) | Returns the first value for a field name |
formData.getAll(name) | Returns all values (for checkboxes, multi-selects) |
formData.set(name, value) | Sets or replaces a field value |
formData.entries() | Returns an iterator of [name, value] pairs |
FormData automatically includes every form control with a name attribute: <input>, <textarea>, <select>, and even file inputs. Checkboxes that are unchecked are excluded, so use formData.has("name") to check for their presence.
Validating Before Submission
HTML validation attributes handle simple rules, but JavaScript validation handles the rest. A small helper function can check the rules and return a list of problems:
function validateForm(formData) {
const errors = [];
if (!formData.get("email").includes("@")) {
errors.push("Email must contain an @ symbol.");
}
if (formData.get("password").length < 8) {
errors.push("Password must be at least 8 characters.");
}
return errors;
}The submit handler calls validateForm() and only blocks the submission when it finds problems. If the array is empty, the form proceeds normally:
form.addEventListener("submit", (event) => {
const errors = validateForm(new FormData(form));
if (errors.length > 0) {
event.preventDefault();
console.log("Validation errors:", errors);
return;
}
console.log("All valid. Ready to send.");
});For the best user experience, show validation errors next to the relevant fields, not just in the console. Use event.preventDefault() only when validation fails; let the form submit normally if everything passes.
Sending Form Data Without a Page Reload
The most common reason to intercept a form submission is to send the data via JavaScript while keeping the user on the current page. FormData works directly as the body of a fetch request, and the browser automatically sets the Content-Type header to multipart/form-data, which is what servers expect from form submissions:
async function submitForm(formData) {
const response = await fetch("/api/submit", {
method: "POST",
body: formData,
});
return response.ok;
}The submit handler prevents the reload, calls submitForm(), and reports the outcome, wrapping the call in try/catch since network requests can fail:
form.addEventListener("submit", async (event) => {
event.preventDefault();
try {
const success = await submitForm(new FormData(form));
console.log(success ? "Form submitted successfully." : "Server returned an error.");
if (success) form.reset();
} catch (error) {
console.log("Network error:", error.message);
}
});Some APIs expect JSON instead of multipart/form-data. In that case, convert the FormData to a plain object with Object.fromEntries() and send it with a JSON Content-Type header:
const data = Object.fromEntries(formData.entries());
const response = await fetch("/api/submit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});form.submit() vs form.requestSubmit()
These two methods trigger a form submission programmatically, but they behave differently:
form.submit() | form.requestSubmit() | |
|---|---|---|
Fires the submit event? | No | Yes |
| Runs HTML validation? | No | Yes |
| Runs your JS handler? | No | Yes |
// This skips your submit handler entirely:
form.submit();
// This triggers the submit event, which runs your handler:
form.requestSubmit();Always prefer requestSubmit() when you want your custom submit logic to run. Use submit() only when you intentionally want to bypass all event handlers and validations, which is rare.
Common Mistakes
Attaching the listener to the button instead of the form. The button click may trigger the form submission, but the submit event fires on the form. Listen on the form element:
// Wrong:
button.addEventListener("click", handleSubmit);
// Right:
form.addEventListener("submit", handleSubmit);This is the same principle behind /javascript/event-target-vs-currenttarget-in-javascript: event.target is the button, but the submit event fires on the form.
Using form.submit() and expecting the handler to run. form.submit() skips all event handlers. If you call it after setting up a submit listener, the listener never fires. Use form.requestSubmit() instead.
Forgetting event.preventDefault() when using fetch. If you call fetch inside a submit handler without preventDefault(), the browser also submits the form the traditional way, causing a page reload. For more on this, see /javascript/using-preventdefault-in-javascript-full-guide.
Not handling the promise rejection from fetch. Network requests can fail. Always wrap fetch in a try/catch or use .catch() to handle errors gracefully.
Rune AI
Key Insights
- Attach the submit listener to the form element, not the button.
- Call event.preventDefault() to stop the page from reloading.
- Use new FormData(form) to collect all input values at once.
- form.requestSubmit() triggers the submit event; form.submit() does not.
- Combine HTML validation attributes with JavaScript checks for the best UX.
Frequently Asked Questions
Why does my form submit handler not run on a button click?
What is the difference between form.submit() and form.requestSubmit()?
How do I get all form values at once?
Conclusion
Handling form submissions in JavaScript gives you control over what happens when a user submits data. Listen for the submit event on the form element, call event.preventDefault() to block the browser's default behavior, read values with FormData, validate them, and decide what to do next.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.