skip to content

When a browser submits a plain HTML form, which controls actually end up in the submitted data, and what excludes a control from it?

level: juniorimportance: must knowfreq 62%

answer

  1. no name, no entry
  2. id is for labels, not the wire
  3. disabled is skipped, readonly is not
  4. unchecked boxes send nothing at all
  5. the form attribute joins from outside

basics

~20 s

Only enabled controls that have a non-empty name attribute are submitted. Missing name, disabled, and unchecked checkboxes or radios all send nothing; readonly fields are still sent, and a checkbox with no value attribute sends the string "on".

solid answer

~50 s

On submit the browser builds an **entry list** of name/value pairs from the form's controls, and everything else — the query string for GET, the request body for POST — is just a serialization of that list. A control joins the list only if it has a non-empty `name`; `id` is for labels and scripts and never appears in the payload. `disabled` controls are skipped entirely, while `readonly` ones are still submitted. Unchecked checkboxes and unchecked radios contribute nothing at all, which is why servers must treat "absent" as false rather than expecting `false`; a checked checkbox with no `value` attribute submits `on`. Several controls may share a name — a radio group produces one entry, a checkbox group produces several. And a control does not have to sit inside the `<form>` tags: `form="formId"` attaches it from anywhere in the document.

go deeper

for a junior

Be ready to say plainly that a control needs a name attribute to be submitted, that id is not it, and that disabled controls and unchecked checkboxes send nothing.

for a middle

Explain the entry-list step that happens before any encoding, and walk through the exclusions and their consequences: readonly still submits, an unnamed field vanishes silently, a valueless checkbox sends on.

for a senior

Show how you debug a missing field in a real form — read the request payload, rebuild the entry list in the console — and explain why readonly and disabled are presentation, never a server-side guarantee.

for a principal

Own the convention: agree how absent-means-false fields are represented across the codebase, whether hidden companion inputs or an explicit field manifest, so back ends stop inventing per-endpoint rules for the same markup.

## Submission is a two-step process It helps to separate the two things the browser does. First it **constructs the entry list**: an ordered list of name/value pairs gathered by walking the form's controls in tree order. Only then does it **serialize** that list — into a URL query string for `method="get"`, into a percent-encoded or multipart body for `method="post"`. Almost every "my field didn't arrive" bug is a step-one bug, so the useful mental model is: *what gets into the entry list?* ## The name attribute is the key, not id A control contributes an entry only if its `name` attribute is present and non-empty. `id` plays no part in submission — it exists so a `<label for>` can point at the control and so scripts and fragment links can find it. ```html <label for="email">Email</label> <input id="email" name="email" type="email"> ``` Here `email=…` is submitted because of `name`, not because of `id`. Drop the `name` and the field silently vanishes from the payload while the page keeps looking correct — one of the most common form bugs there is. ## What is excluded, and what only looks excluded - **`disabled`** — excluded. A disabled control produces no entry (it is also skipped by validation). If you want the value greyed out *and* sent, use `readonly` plus a hidden field, or re-enable the control just before submit. - **`readonly`** — included. Readonly means the user cannot edit it, not that the server won't see it. Never rely on `readonly` for anything security-relevant; the value is still user-controlled at the wire. - **Unchecked checkbox or radio** — no entry. There is no `false`, no empty string, nothing. Server code must treat absence as "off". A common companion trick is a hidden input with the same name placed *before* the checkbox, so the server always receives at least one value. - **A checked checkbox with no `value`** — submits the string `on`. That default surprises people who expect `true`; give the checkbox an explicit `value` when the wire format matters. - **Buttons** — only the button that actually triggered the submission contributes its `name`/`value`. Other submit buttons, and every `type="button"` or `type="reset"` button, contribute nothing. - **`type="hidden"`** — always included when it has a name. That is exactly its job: carrying state such as a record id through a round trip. ## Repeated names are normal Names are not required to be unique. A radio group is *defined* by a shared name, and it yields a single entry for whichever radio is checked. A checkbox group with a shared name yields one entry per checked box, in document order: ```html <input type="checkbox" name="topic" value="html" checked> <input type="checkbox" name="topic" value="css"> <input type="checkbox" name="topic" value="a11y" checked> ``` That submits `topic=html&topic=a11y`. Server frameworks surface repeated names as a list, and on the client `FormData.prototype.getAll("topic")` returns both values while `get("topic")` returns only the first. Any code that flattens entries into a plain object — `Object.fromEntries` included — quietly keeps only the last occurrence of each name, so multi-value fields need `getAll`. ## A control does not have to be inside the form element Every control has a **form owner**. Normally that is the nearest ancestor `<form>`, but the `form` attribute overrides it by id: ```html <form id="checkout" action="/orders" method="post"> … </form> <input name="coupon" form="checkout"> ``` The coupon field submits with the checkout form even though it lives elsewhere in the document. This is what saves you when layout or a table structure makes nesting impossible, and it means "is it inside the tags?" is not the real test of membership. ## How to check quickly Two reliable ways to see the truth rather than guess it. In DevTools, submit the form and read the request's form-data payload. In the console, build the same entry list the browser would: ```js const form = document.querySelector("#checkout"); for (const [name, value] of new FormData(form)) { console.log(name, value); } ``` If a field is missing from that loop, it will be missing on the server too, and the cause is almost always a missing `name` or a stray `disabled`.

  • How should a server distinguish "the user unchecked this box" from "this field was never on the form"?
    It cannot, from the payload alone — both look identical, since an unchecked box sends nothing. You make it explicit in the markup: put a hidden input with the same name and a falsy value immediately before the checkbox, so an unchecked box yields just the hidden value and a checked one yields both, with the later entry winning. Alternatively, send an explicit list of the fields the form contained.
  • A field is greyed out with disabled but the team still needs its value server-side. What do you do?
    Do not rely on `disabled` — it removes the control from the entry list. Either use `readonly`, which keeps the value in the payload while blocking editing, or keep the visible control disabled and add a `type="hidden"` input carrying the same name and value. Whichever you choose, revalidate server-side: the client can change either one.
  • Why is it legal, and often intentional, for several controls to share the same name?
    Because the entry list is a list, not a map. A radio group is defined by a shared name — that is what makes the options mutually exclusive — and a checkbox group uses a shared name to submit several values under one key. On the client, read them with `formData.getAll(name)`; flattening into an object keeps only the last one.

saying these in an interview costs you the question

  • Thinks the id attribute is what names the field on the wire
  • Expects an unchecked checkbox to submit false or off
  • Thinks readonly fields are omitted just like disabled ones
  • Assumes every button in the form submits its name and value
  • Believes a control must be nested inside form tags to participate

context