Before JavaScript sent a single request, the <form> element was how the web talked back - and its design decisions still govern how nearly every request body on the internet is shaped. A form declares three things: where (action), how (method, and the element only speaks GET and POST), and in what wire format (enctype). This article follows the input from the field to the wire; the curl builder lets you forge the same requests by hand.

GET: answers in the address

method="GET" serializes the fields into the query string: ?q=fable&lang=pt, names and values percent-encoded, pairs joined by & - with the form-heritage quirk that spaces become + in this context. The consequences come as a package: the request is bookmarkable, shareable, cacheable, and safely retried - and every value lands in browser history, server logs, and Referer headers, with practical URL length ceilings on top. The rule writes itself: GET for searches and filters, never for secrets, never for state changes a crawler might click.

POST + urlencoded: the same pairs, moved indoors

method="POST" with the default enctype="application/x-www-form-urlencoded" ships the identical name=value&name=value serialization - just relocated into the request body, with Content-Type: application/x-www-form-urlencoded announcing the dialect. Same encoding, different address: values leave the URL (and its logs and history), size ceilings effectively vanish, and the request stops being idempotent by contract. Login forms live here. One habit-forming detail for anyone who reads packet captures: the body of a urlencoded POST looks exactly like a query string, because it is one - a fact the curl data-flags article exploits deliberately.

Multipart: envelopes for files

Key-value chains cannot carry a photograph. enctype="multipart/form-data" switches to an envelope format: the Content-Type header declares a boundary string, and the body becomes parts separated by that boundary - each part with its own mini-headers (Content-Disposition: form-data; name="avatar"; filename="me.jpg", its own Content-Type) and raw payload, no percent-encoding needed. It is heavier per field and mandatory for <input type="file">; the classic failure is forging one by hand and hand-setting the Content-Type without the boundary the body actually uses - the server receives an envelope it cannot cut open. (The third enctype, text/plain, exists and belongs in no production request.)

The fetch-era footnote

Forms stopped being the only sender when XHR and fetch arrived, but the formats outlived the element: URLSearchParams builds urlencoded bodies, FormData builds multipart ones (boundary handled for you - do not set the header yourself), and JSON joined as the API era's third dialect. The durable skill is reading Content-Type as the body's declared grammar and knowing all three by sight: q=a&r=b, boundary-cut parts, and {"q":"a"} are the same intent in three costumes - and half of all integration bugs are one side wearing the wrong one.