How form data reaches PHP
An HTML form collects input and sends it to the URL in its action attribute. PHP reads the fields and makes them available in two superglobal arrays, which exist everywhere in your code:
$_GETholds fields sent in the URL's query string (?q=mug&page=2).$_POSTholds fields sent in the body of a POST request.
Each field's name attribute becomes the array key. If you have not met forms before, the HTML course has a lesson on them.
GET: data in the URL
With method="get" (the default), the browser adds the fields to the address. Here is a search page that shows the form and, when a search was made, the results:
<?php
$products = ['Mechanical Keyboard', 'Ceramic Mug Set', 'Travel Mug', 'Water Bottle'];
$q = trim($_GET['q'] ?? '');
$results = $q === '' ? [] : array_filter($products, fn($p) => stripos($p, $q) !== false);
?>
<form action="/search.php" method="get">
<input name="q" value="<?= htmlspecialchars($q) ?>">
<button>Search</button>
</form>
<?php if ($q !== ''): ?>
<p><?= count($results) ?> result(s) for "<?= htmlspecialchars($q) ?>"</p>
<?php foreach ($results as $product): ?>
<p><?= htmlspecialchars($product) ?></p>
<?php endforeach; ?>
<?php endif; ?>When a visitor types "mug" and presses Search, the browser requests /search.php?q=mug:
GET /search.php?q=mug<form action="/search.php" method="get">
<input name="q" value="mug">
<button>Search</button>
</form>
<p>2 result(s) for "mug"</p>
<p>Ceramic Mug Set</p>
<p>Travel Mug</p>Three details are worth copying. $_GET['q'] ?? '' gives an empty string on the first visit, when there is no q, instead of a warning. trim() removes stray spaces. And the value is printed back into the input, escaped, so the visitor sees what they searched for.
Because the search is in the URL, it can be bookmarked, shared and opened in a new tab. That is exactly right for searches, filters, sorting and pagination: anything that only reads data.
POST: data in the request body
With method="post", the fields travel in the body of the request, not in the address. Use POST for anything that changes something (placing an order, signing up, sending a message) and for private data such as passwords, which must never appear in URLs, browser history or server logs.
A page usually needs to know whether it is being shown for the first time or receiving a submission. $_SERVER['REQUEST_METHOD'] tells you:
<?php
$sent = false;
$name = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$message = trim($_POST['message'] ?? '');
// Here you would validate, then save the message or email it.
$sent = $name !== '' && $message !== '';
}
?>
<?php if ($sent): ?>
<p>Thanks, <?= htmlspecialchars($name) ?>. We will reply soon.</p>
<?php else: ?>
<form method="post" action="/contact.php">
<input name="name" value="<?= htmlspecialchars($name) ?>">
<textarea name="message"></textarea>
<button>Send</button>
</form>
<?php endif; ?>The first visit is a GET, so the form is shown:
GET /contact.php<form method="post" action="/contact.php">
<input name="name" value="">
<textarea name="message"></textarea>
<button>Send</button>
</form>Submitting sends a POST request. This is what the browser sends, with the fields encoded in the body:
POST /contact.php
Content-Type: application/x-www-form-urlencoded
name=Liam+O%27Brien&message=Do+you+ship+to+Dublin%3F<p>Thanks, Liam O'Brien. We will reply soon.</p>The browser encoded the space as + and the apostrophe and question mark as %27 and %3F; PHP decoded them before filling $_POST, and htmlspecialchars() escaped the apostrophe again for HTML. A real page would then redirect the visitor, so that refreshing does not send the form twice; Superglobals, headers and redirects shows how.
Everything is a string
Every value in $_GET and $_POST is a string, even when the input was a number. Convert and validate it before you use it, as the next lesson shows:
<?php
var_dump($_POST['quantity'] ?? null);
var_dump($_POST['newsletter'] ?? null);POST /types.php
Content-Type: application/x-www-form-urlencoded
quantity=3string(1) "3"
NULLCheckboxes and several values
Two kinds of field need special care. A checkbox is only sent when it is ticked, so an unticked one is simply missing; that is why newsletter above is NULL. Test it with isset().
When one field can have several values, such as a group of checkboxes or a <select multiple>, put square brackets at the end of its name. PHP then collects the values into an array:
<?php
$newsletter = isset($_POST['newsletter']);
$topics = $_POST['topics'] ?? [];
if (!is_array($topics)) {
$topics = [];
}
$allowed = ['php', 'mysql', 'css'];
$topics = array_values(array_intersect($topics, $allowed));
echo "Newsletter: " . ($newsletter ? "yes" : "no") . "\n";
echo "Topics: " . (implode(", ", $topics) ?: "none") . "\n";The form would contain <input type="checkbox" name="newsletter"> and several <input type="checkbox" name="topics[]" value="php">. Submitting it with two topics ticked:
POST /preferences.php
Content-Type: application/x-www-form-urlencoded
newsletter=on&topics%5B%5D=php&topics%5B%5D=mysqlNewsletter: yes
Topics: php, mysql%5B%5D is the encoded form of []. The is_array() check and the list of allowed values matter: anyone can send any request, not just through your form, so never assume the data has the shape your form would produce.
$_REQUEST
PHP also has $_REQUEST, which merges GET, POST and cookie values. Avoid it: you lose track of where a value came from, and a value in the URL could quietly override the one you expected in the body.
Common mistakes
- Reading $_POST['field'] directly without
??orisset(), which warns when the field is missing. - Sending passwords or personal data with GET, which puts them in the URL and logs.
- Treating form values as numbers or booleans without converting and validating them.
- Printing submitted values back without htmlspecialchars(), which allows XSS.
Exercise
Create bmi.php that shows a GET form with height (cm) and weight (kg). When both are present and numeric, print the BMI to one decimal place below the form; otherwise print nothing. Test it with /bmi.php?height=175&weight=70.
Show one possible answer
<?php
$height = $_GET['height'] ?? '';
$weight = $_GET['weight'] ?? '';
$bmi = null;
if (is_numeric($height) && is_numeric($weight) && $height > 0) {
$bmi = $weight / (($height / 100) ** 2);
}
?>
<form method="get">
<input name="height" value="<?= htmlspecialchars($height) ?>">
<input name="weight" value="<?= htmlspecialchars($weight) ?>">
<button>Calculate</button>
</form>
<?php if ($bmi !== null): ?>
<p>Your BMI is <?= number_format($bmi, 1) ?></p>
<?php endif; ?>GET /bmi.php?height=175&weight=70<form method="get">
<input name="height" value="175">
<input name="weight" value="70">
<button>Calculate</button>
</form>
<p>Your BMI is 22.9</p>This site has a full BMI calculator that runs in the browser instead.
Key points
- A form's method decides where the data arrives: method="get" puts it in the URL and $_GET; method="post" sends it in the body and $_POST.
- Use GET for searches and filters that can be bookmarked; use POST for anything that changes data or is private.
- Every value arrives as a string (or an array for name="x[]"). Unchecked checkboxes are not sent at all.
- Read fields with $_POST['name'] ?? '' and check $_SERVER['REQUEST_METHOD'] to tell a first visit from a submission.