The form looks right, the user swears they filled everything in, and the payload that reaches your handler is missing half of it. Before you dig through your server logs, check whether the browser sent those fields at all. Usually it did not.
Four reasons a control is left out
A form control is excluded from the submitted data if it has the disabled attribute, if it has no name attribute, if it is a checkbox or radio that is not checked, or if it sits inside a fieldset that is disabled. You can watch all four happen at once:
const { JSDOM } = require('jsdom')
const dom = new JSDOM(`
<form id="signup">
<input name="firstName" value="Ada">
<input name="lastName" value="Lovelace" disabled>
<input value="no name attribute">
<input name="plan" type="checkbox" value="pro">
<input name="terms" type="checkbox" value="yes" checked>
<input name="notes" value="prefilled" readonly>
<fieldset disabled>
<input name="referral" value="never sent">
</fieldset>
</form>`)
const form = dom.window.document.getElementById('signup')
console.log([...new dom.window.FormData(form).entries()])That prints three entries: firstName, terms, and notes. The disabled lastName, the input with no name, the unchecked plan checkbox, and referral inside the disabled fieldset are all dropped without a warning anywhere.
disabled is the one that bites
The common version of this bug: you have a prefilled field you do not want the user editing, so you add disabled. That also removes it from the submission. MDN puts the distinction plainly, noting that read-only controls can still function and are still focusable, whereas disabled controls cannot receive focus and are not submitted with the form. Use readonly instead. A readonly field keeps its value, stays in the tab order, and still submits.
The fieldset case is the same rule one level up. Per the HTML standard, a control is disabled if it descends from a fieldset carrying the disabled attribute, unless it sits inside that fieldset's first legend child. Disable a fieldset to gray out a section visually and you have also deleted that section from your payload.
The fix
Give every control you expect back a name attribute, since the name is what the submission algorithm keys on. Swap disabled for readonly on prefilled fields you still need. Treat an absent checkbox key as false on the server rather than as a validation error, because an unchecked box sends nothing at all. And if you disable inputs from JavaScript, during a submit-in-progress state or a multi-step form, re-enable them before the form serializes, or mirror their values into hidden inputs.
Back to All Questions