In PHP, what is the difference between $_GET and $_POST, and when is each of them populated?
answer
- query string versus request body
- $_GET is filled for any method
- $_POST needs the POST method
- form-urlencoded or multipart/form-data only
- strings or nested arrays, never trusted
basics
~10 s$_GET holds the URL's query-string parameters for any request method; $_POST holds fields parsed from a POST body sent as application/x-www-form-urlencoded or multipart/form-data. Both are client-supplied, so neither is trusted.
solid answer
~50 s`$_GET` is built from the **query string** — everything after `?` in the URL — and is filled whenever a query string is present, whatever the method, so `POST /book?ref=mail` fills both arrays. `$_POST` is built from the **request body**, and only when the method is `POST` and the `Content-Type` is `application/x-www-form-urlencoded` or `multipart/form-data`. A JSON body, or a form body sent with `PUT`, leaves `$_POST` empty; the raw bytes are in `php://input` instead. Both arrays map names to strings, or to nested arrays when a field name uses brackets, so a value must be type-checked before use — `$_GET['page']` can be an array. Neither is safer than the other: both come straight from the client. The real difference is how they are used: GET for reads that are safe to repeat and bookmark, POST for changes.
code
php · 15 lines<?php
declare(strict_types=1);
// POST /rooms/hold?ref=newsletter with body: room=12&nights=3
$ref = $_GET['ref'] ?? null; // "newsletter" - from the query string
$room = $_POST['room'] ?? null; // "12" - from the form body, still a string
$nights = $_POST['nights'] ?? null;
if (!is_string($room) || !ctype_digit($room) || !is_string($nights) || !ctype_digit($nights)) {
http_response_code(422);
exit('Invalid room or nights');
}
$roomId = (int) $room;
$count = (int) $nights;go deeper
Recall that $_GET comes from the query string and $_POST from a form-encoded or multipart POST body, and that both are untrusted.
Explain exactly when $_POST is populated, why values are strings or arrays, and why a JSON or PUT body leaves $_POST empty.
Treat both arrays as untrusted input with type checks at the boundary, and keep state-changing actions on POST so links and prefetches cannot trigger them.
Set the convention for how request input enters the codebase, such as one typed request object built from the superglobals, so raw arrays do not spread.
## Where each array comes from PHP builds its request **superglobals** before your script starts. They are ordinary arrays that are visible in every scope — inside functions and methods too — without a `global` statement. | | `$_GET` | `$_POST` | |---|---|---| | Source | The URL's query string, after `?` | The request body | | Filled when | A query string is present, for **any** method | The method is `POST` **and** the body is form-encoded or multipart | | Typical sender | Links, bookmarks, search forms with `method="get"` | Forms with `method="post"` | | Typical use | Filters, pagination, search terms | Creating, changing or deleting data | The name `$_GET` is historical: it means "query-string parameters", not "parameters of a GET request". A `POST` to `/rooms?city=Lisbon` has `$_GET['city']` **and** whatever the body carried in `$_POST`. ## When $_POST stays empty PHP parses the body into `$_POST` only for two content types — `application/x-www-form-urlencoded` and `multipart/form-data` — and only when the method is `POST`. It stays an empty array when: - the client sends JSON, XML or any other content type — read `php://input` instead; - the method is `PUT`, `PATCH` or `DELETE`, even with a form-encoded body; - the body was larger than the configured POST size limit, in which case PHP discards it and raises a warning; - the `enable_post_data_reading` directive is Off, which leaves the body for the script to read itself. An empty `$_POST` is therefore not proof that "nothing was sent". ## Values are strings or arrays Every leaf value in both arrays is a **string** — `?nights=3` gives `"3"`, not `3`. A parameter name with brackets produces a nested array, so a client can turn any expected string into an array by changing the URL: ```php // GET /rooms?city[]=x $city = $_GET['city'] ?? ''; strlen($city); // TypeError: strlen(): Argument #1 ($string) must be of type string, array given ``` Since PHP 8.0 passing an array where a string is expected throws a `TypeError`, so unchecked input can turn into an uncaught error. Check the type before use: 1. Read with a default: `$_GET['page'] ?? '1'`. 2. Confirm the type: `is_string($value)`. 3. Convert or validate explicitly, for example with `filter_var()`, before using the value as a number, an id or a path. ## Neither is trusted A common junior belief is that `$_POST` is safer because it is "hidden". It is not: anyone can send any body with any tool, just as they can edit a URL. Both arrays carry **untrusted input**, and so do `$_COOKIE`, most of `$_SERVER` and `php://input`. Validation and escaping rules apply equally to all of them. ## Choosing GET or POST - Use query parameters for requests that **read** data and can be repeated, cached, shared and bookmarked. - Use a POST body for requests that **change** data, so that a crawler following links or a browser prefetch cannot trigger them. - Do not put secrets such as passwords or tokens in the query string: URLs end up in server access logs, browser history and `Referer` headers. ## Missing keys A parameter the client did not send is simply absent from the array. Reading `$_GET['page']` when there is no `page` raises the warning `Undefined array key "page"` (a notice before PHP 8.0) and yields `null`. Two idioms avoid it: - `$_GET['page'] ?? '1'` supplies a default without any warning; - `isset($_POST['confirm'])` or `array_key_exists('confirm', $_POST)` tests for presence explicitly. Presence and emptiness are different questions: `?q=` sends `q` as the empty string `""`, which is present, while omitting `q` leaves the key out entirely. Search forms, filters and optional flags usually need to tell those two cases apart. ## Writing to superglobals The arrays can be modified like any array, and some frameworks do so when building a request object. Changing `$_GET` affects only the array in the current request; it does not change the URL or any other superglobal. Treat them as read-only input and copy what you need into typed variables.
- Is $_GET empty for a POST request?No. `$_GET` is built from the query string whenever one is present, regardless of the request method. A `POST /book?ref=mail` request has `$_GET['ref']` set and its form fields in `$_POST`. The name reflects the query string, not the method.
- Why can $_GET['city'] be an array, and what goes wrong if you assume a string?A client can write `?city[]=x`, and PHP turns bracketed names into nested arrays. Passing that array to a string function such as `strlen()` or `trim()` throws a `TypeError` in PHP 8. Always check `is_string()` or validate the value before using it as a string.
saying these in an interview costs you the question
- $_POST is secure because users cannot see or edit the body
- $_GET is only populated for requests using the GET method
- $_POST is filled for any body, including JSON
- Query-string values arrive as integers when they look numeric
- A PUT request with a form-encoded body fills $_POST