After r.ParseForm in a Go handler, what is the difference between r.Form and r.PostForm?
answer
- one field is body-only
- the other is a merge
- who wins on a duplicated key
- Content-Type decides whether the body is parsed at all
- FormValue reads the merge and eats the error
basics
~10 sr.PostForm holds only values parsed from a urlencoded request body. r.Form holds those plus the URL query string merged in, with body values taking precedence. r.FormValue reads r.Form; r.PostFormValue reads r.PostForm.
solid answer
~40 s`r.ParseForm()` always parses the URL query string, and for POST, PUT and PATCH it additionally reads the body and parses it — but only when the `Content-Type` is `application/x-www-form-urlencoded`. Body values land in `r.PostForm`; `r.Form` gets the union of body and query values, with the body value first for a duplicated key, so `r.FormValue("limit")` returns the body's value when both sides send `limit`. Both are `url.Values`, so `Get` gives the first value and indexing gives repeats. The distinction matters for security and clarity: if an endpoint means "the body said so", read `r.PostForm` or `r.PostFormValue`, otherwise a caller can smuggle the parameter in through the query string. `r.FormValue` calls `ParseForm` implicitly and discards its error, so call `ParseForm` yourself when you want to answer 400 on a malformed body.
code
go · 9 linesif err := r.ParseForm(); err != nil {
http.Error(w, "malformed form", http.StatusBadRequest)
return
}
r.PostForm.Get("limit") // "50" - body only
r.Form.Get("limit") // "50" - the body value precedes the query value
r.Form.Get("x") // "2"
r.PostForm.Get("x") // "2"
r.PostForm.Get("page") // "" - a query-only key never reaches PostFormgo deeper
Recall which field is which: PostForm comes from the body, Form is body plus query. Remember that you must call ParseForm first, or use FormValue, which parses for you.
Explain the mechanics: which methods and which Content-Type make ParseForm read the body, that body values precede query values in r.Form, and that FormValue discards the parse error.
Demonstrate the judgment of reading PostForm when the contract says body, so query parameters cannot be smuggled in, and of calling ParseForm explicitly so malformed input becomes a 400 rather than an empty string.
Own the contract question: whether an endpoint accepts urlencoded forms at all, or standardises on JSON, and what that decision costs in browser support, logging exposure and the amount of per-handler input code your teams must keep consistent.
## The two fields `*http.Request` carries two parsed-input fields of type `url.Values` (`map[string][]string`), both of them nil until something populates them: - **`r.PostForm`** — parameters that came from the **request body**, parsed as `application/x-www-form-urlencoded`. - **`r.Form`** — the **merge** of those body parameters with the parameters in the URL query string. `r.ParseForm()` is what fills them. ## What ParseForm actually does For every request method, `ParseForm` parses `r.URL.RawQuery` into `r.Form`. For `POST`, `PUT` and `PATCH` it also looks at the `Content-Type` header: - `application/x-www-form-urlencoded` — it reads the body to completion, parses it, and puts the result in `r.PostForm`, then copies those values into `r.Form` before appending the query values. - `multipart/form-data` — `ParseForm` does **not** parse it; that is `ParseMultipartForm`'s job. - anything else, `application/json` included — the body is left completely untouched, `r.PostForm` ends up empty, and `ParseForm` returns a **nil error**. Nothing tells you the body was ignored. That last bullet is the trap. A handler that posts JSON and then calls `r.FormValue("id")` gets `""` with no complaint at all. ## Precedence when a key appears on both sides Send `POST /search?limit=10` with the body `limit=50`. After `ParseForm`: - `r.PostForm["limit"]` is `["50"]` - `r.Form["limit"]` is `["50", "10"]` — body first, query appended - `r.Form.Get("limit")` and `r.FormValue("limit")` therefore return `"50"` Body parameters take precedence in `r.Form`. Both values survive in the slice, so a handler that cares can detect the collision by checking `len(r.Form["limit"]) > 1`. ## Which one should a handler read? This is a real API-design decision, not a style preference. If your endpoint's contract is "the parameters are in the body", read `r.PostForm` / `r.PostFormValue`. If you read `r.Form` instead, a caller can supply a parameter your endpoint expected in the body by putting it in the URL — which shows up in access logs, in referrers, and in any cache key, and lets a crafted link carry a parameter the designer assumed only a form could send. Conversely, an endpoint that deliberately accepts either (a search form that also works as a bookmarkable URL) is exactly what `r.Form` is for. ## The convenience wrappers - `r.FormValue(key)` calls `ParseMultipartForm`/`ParseForm` if needed, then returns `r.Form.Get(key)`. - `r.PostFormValue(key)` does the same parsing, then returns `r.PostForm.Get(key)`. Both **swallow the parse error**. That is fine for a throwaway handler and wrong for an API that owes clients a 400 on garbage input: call `r.ParseForm()` explicitly, check its error, and read the fields afterwards. `ParseForm` reports a malformed query string as well as a malformed body, so one check covers both. ## Other details worth knowing - Both fields are `url.Values`, so all the query-string rules carry over: `Get` returns the first value or `""`, indexing returns every repeat, and `Has` reports presence. - `ParseForm` is idempotent — calling it twice does no extra work, because it returns early once `r.Form` is non-nil. - The urlencoded body read is itself size-capped by `net/http` (on the order of 10 MB); for a tighter, explicit cap on any body, wrap `r.Body` in `http.MaxBytesReader` before parsing. - On the client side of the same coin, `url.Values.Encode()` produces the very body format `ParseForm` consumes, which is what `http.PostForm` sends. ## Interview-ready summary `PostForm` = body only. `Form` = body plus query, body wins. `ParseForm` only parses a body that is urlencoded and only for POST/PUT/PATCH, and it reports nothing when it skips one. Read `PostForm` when the contract says "body", and call `ParseForm` explicitly so you can act on its error.
- A handler posts JSON and calls r.FormValue("id"), which returns empty. Why is there no error?`ParseForm` only parses a body whose `Content-Type` is `application/x-www-form-urlencoded`. With `application/json` it parses the query string, leaves the body untouched, and returns a nil error, so `r.PostForm` is empty and `r.FormValue` returns `""`. Nothing in the API reports that the body was skipped — decode JSON bodies with `json.NewDecoder(r.Body)` instead.
- Why might reading r.Form rather than r.PostForm be a security problem?`r.Form` merges the URL query string in, so a parameter your endpoint expected in a form body can be supplied through the URL by anyone who can make the victim's browser follow a link. It also puts that parameter into access logs and cache keys. If the contract is "this comes from the body", read `r.PostForm` or `r.PostFormValue`.
- How do you notice that a client sent the same key in both the query string and the body?Index rather than `Get`: after `ParseForm`, `r.Form["limit"]` holds every occurrence — the body value first, then the query value — so `len(r.Form["limit"]) > 1` means the client sent it twice. `Get` and `FormValue` only ever show you the first, which is why collisions go unnoticed.
saying these in an interview costs you the question
- Says r.Form is body-only and r.PostForm includes the query
- Thinks the URL query value wins over the body value in r.Form
- Believes ParseForm parses a JSON body
- Expects an error when the Content-Type is not urlencoded
- Relies on FormValue and never checks the ParseForm error
- Assumes ParseForm handles multipart/form-data too