A PHP form posts back to the script that printed it: write <form method="post"> with named inputs, and when $_SERVER['REQUEST_METHOD'] is 'POST', read each field as $_POST['name']. Escape anything you print back with htmlspecialchars().
Run this block, type a name into the form and press Send: the same script runs again, this time with $_POST filled in.
Three things happen here that every form handler needs:
- The method check. On the first visit the request is a GET and there is nothing to process.
?? 'GET'only matters when the file runs from the command line, whereREQUEST_METHODis not set; on a web server it always is. ?? ''on every field. A field can be missing (a renamed input, a request sent by a script), and reading a missing key prints a warning.- Escaping on output. The name goes back into the page twice, and
htmlspecialchars()makes sure a value like<b>Ada</b>is shown as text instead of becoming markup. The details are on the htmlspecialchars page.
Validate a form and show errors next to fields
Collect errors in an array keyed by field name. If the array is empty, the form is valid; otherwise print the form again with each message under its field and the visitor's input still in place. Submit it empty, then with a bad email, then with valid values.
novalidate turns off the browser's own checks so you can watch the PHP ones work. Keep both in a real form: browser validation is a convenience for the visitor, PHP validation is the one that counts, because anyone can send a request without your form. filter_var() does the email and the integer range in one call each; the filter_var page covers its other filters.
Checkboxes, radio buttons and select
A text field always sends a value, even an empty one. The other controls do not:
- An unchecked checkbox sends nothing at all, so its key is missing from
$_POST. - Checkboxes named
topics[]arrive as an array:$_POST['topics']is['php', 'sql']. - A radio group or a
<select>sends thevalueof the chosen option.
To keep the choice after submit, print checked or selected on the option that matches. Tick a few boxes, pick a level and press Save.
The two checks after reading the input matter more than they look. array_intersect() drops any topic that was not on your list, and the is_string() plus array_key_exists() check puts the level back to a default if someone sends a value you never offered (or an array, which would make $levels[$level] throw). A select limits what a browser can send, not what a request can contain.
Multi-step forms with hidden fields
A hidden input carries a value from one step to the next without showing it. Here step 1 asks for a name, step 2 asks for a language and sends the name along in a hidden field, and step 3 shows both. Go through all three steps.
Hidden fields are visible in the page source and editable like any other field, so they are fine for carrying the visitor's own answers between steps, but never for a price, a user id or a role. Keep those on the server, in a session; see sessions and cookies.
Post/Redirect/Get: stop the resubmit on refresh
After a successful POST, if the script prints a "thank you" page directly, pressing refresh asks the browser to send the POST again, and the order or comment is saved twice. The fix is to save, redirect, and show the result on a GET:
<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$errors = validate($_POST);
if (!$errors) {
save_comment($_POST);
$_SESSION['flash'] = 'Comment posted.';
header('Location: /comments.php', true, 303);
exit;
}
// On errors, fall through and print the form with the messages.
}
$flash = $_SESSION['flash'] ?? null;
unset($_SESSION['flash']);
303 See Other tells the browser to follow the redirect with a GET. exit after header() matters: without it the rest of the script keeps running. The redirect also has to happen before any output, or PHP refuses with "headers already sent".
Real forms also add a CSRF token: a random value stored in the session and in a hidden field, compared with hash_equals() on submit, so another site cannot post to your form on a logged-in visitor's behalf.
Test the validation without a browser
Put the rules in a function that takes an array and returns the errors. The page calls it with $_POST, and you can call it with any array you like, which makes the edge cases easy to try. Add your own cases to the list.
The fourth case is the one people forget. A request can send name[]=x, which makes $_POST['name'] an array, and passing an array to trim() throws a TypeError and stops the script. Checking is_string() first turns hostile input into an ordinary validation error. The (string) cast in the earlier blocks avoids the TypeError too, but it turns the array into the word Array and prints a warning, so is_string() is the cleaner check.
Common mistake: reading $_POST before the form is sent
The most common first form script reads the fields at the top of the file:
<?php
$name = $_POST['name'];
echo "Hello, $name";
On the first visit nothing has been posted, so PHP prints:
Warning: Undefined array key "name" in /var/www/form.php on line 2
The fix is the pattern from the first block: process the form only when the request is a POST, and read every field with a default, $_POST['name'] ?? ''. If the warning appears after submitting, compare the name attribute in the HTML with the key in PHP: name="Name" and $_POST['name'] are different keys.
Frequently Asked Questions
How do I submit a PHP form to the same page?
Write <form method="post"> with no action (or action=""). The browser posts back to the current URL, so the same script shows the form and handles it: check $_SERVER['REQUEST_METHOD'] === 'POST' and read the fields from $_POST.
How do I keep form values after submit in PHP?
Print the submitted value back into the field, escaped: <input name="email" value="<?= htmlspecialchars($_POST['email'] ?? '') ?>">. For a checkbox print checked, and for a select option print selected, when the submitted value matches.
How do I get the values of multiple checkboxes in PHP?
Give the checkboxes the same name ending in brackets, name="topics[]". PHP then puts the checked values in an array, $_POST['topics']. Unchecked boxes send nothing, so use $_POST['topics'] ?? [] and check every value against the list you offered.
Why does refreshing the page resubmit my PHP form?
The last request was a POST, so the browser repeats it on refresh. After a successful submit, redirect with header('Location: /thanks.php'); exit; (the Post/Redirect/Get pattern). The refresh then repeats a harmless GET.
Is $_POST safe to use in PHP?
No, every value in $_POST comes from the visitor and can be anything, including HTML, SQL or an array where you expected a string. Validate it on the way in (filter_var, length and allowlist checks) and escape it on the way out (htmlspecialchars for HTML, prepared statements for SQL).