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.

6 min read

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:

javascriptjavascript
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

Form submission flow with JavaScript interception

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:

javascriptjavascript
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:

javascriptjavascript
for (const [name, value] of formData.entries()) {
  console.log(`${name}: ${value}`);
}
MethodWhat 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: &lt;input&gt;, &lt;textarea&gt;, &lt;select&gt;, 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:

javascriptjavascript
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:

javascriptjavascript
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:

javascriptjavascript
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:

javascriptjavascript
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:

javascriptjavascript
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?NoYes
Runs HTML validation?NoYes
Runs your JS handler?NoYes
javascriptjavascript
// 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:

javascriptjavascript
// 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

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.
RunePowered by Rune AI

Frequently Asked Questions

Why does my form submit handler not run on a button click?

The submit event fires on the form element, not the button. Attach your listener to the form, not the submit button. The event still fires regardless of how the form was submitted, including clicking a button or pressing Enter in an input.

What is the difference between form.submit() and form.requestSubmit()?

form.submit() bypasses the submit event entirely and sends the form directly. form.requestSubmit() triggers the submit event first, which means your JavaScript handler runs before the actual submission. Use requestSubmit() when you want your custom handlers to run.

How do I get all form values at once?

Use the FormData constructor: new FormData(formElement). This creates an object containing all named form fields and their values. You can iterate over it with .entries() or convert it with Object.fromEntries().

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.