Form submission and constraint validation API

HTML · Expert · 14 min

File uploads, custom error messages and progressive enhancement all depend on these details.

When a form submits, the browser builds a list of entries from successful controls (named, enabled, checked if a checkbox or radio) and encodes them according to enctype:

  • application/x-www-form-urlencoded (default): name=Ada+L&tags=a&tags=b, spaces as +, other bytes percent-encoded. With method="get" this becomes the query string.
  • multipart/form-data: each field in its own MIME part. Required for <input type="file">; with urlencoded only the file name is sent.
  • text/plain: for debugging only; ambiguous to parse.

A submit button's name=value is included only for the button that was clicked, and buttons can override the form with formaction, formmethod and formnovalidate. Disabled controls are never sent; use readonly if the value must be submitted.

The constraint validation API exposes the browser's validation to scripts. Each control has validity (flags such as valueMissing, patternMismatch, tooShort, customError), validationMessage, checkValidity() and reportValidity(). setCustomValidity("message") marks a control invalid with your text; set it back to "" or the field stays invalid forever. The novalidate attribute on the form disables native bubbles while keeping the API, which is how custom error UIs are built.

new FormData(form) captures exactly what a native submit would send, and fetch(url, { method: "POST", body: formData }) sends it as multipart. Listening to the form's submit event and calling requestSubmit(), rather than submit(), keeps validation and submit handlers in play.

Example

<form id="f" action="/api/profile" method="post" enctype="multipart/form-data" novalidate>
  <input name="username" required minlength="3">
  <input name="avatar" type="file" accept="image/*">
  <input name="confirm" placeholder="type DELETE">
  <button name="intent" value="save">Save</button>
</form>
<script>
  const f = document.getElementById("f");
  f.addEventListener("submit", async (e) => {
    e.preventDefault();
    const c = f.elements.confirm;
    c.setCustomValidity(c.value === "DELETE" ? "" : "Type DELETE to confirm");
    if (!f.reportValidity()) return;      // shows first error, focuses it
    const data = new FormData(f, e.submitter); // includes intent=save
    const res = await fetch(f.action, { method: "POST", body: data });
    console.log(res.status);
  });
</script>

Remember

  • File uploads need enctype="multipart/form-data"
  • Disabled controls are not submitted; readonly ones are
  • setCustomValidity("") clears a custom error
  • FormData(form, submitter) mirrors a native submit

Watch out for

  • Calling setCustomValidity with a message and never resetting it to an empty string.

Check yourself

  1. A form with the default enctype has a file input. What does the server receive for it?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: developer.mozilla.org/en-US/docs/Web/HTML/Guides/Constraint_validation

Builds on

Unlocks

Nothing builds on this yet.