Handling Forms
Forms are how users send data to your site: search boxes, sign-up pages, contact forms, checkout. The browser sends the field values with the request, and PHP makes them available in the $_GET and $_POST arrays. This lesson builds forms that submit, redisplay and process their data correctly.
GET and POST
A form's method decides how the data travels:
- GET puts the values in the URL's query string (
?q=php&page=2). Use it for searches and filters — anything that just reads data and that people might bookmark or share. - POST sends the values in the request body. Use it for anything that changes data — creating accounts, placing orders, sending messages — and for passwords or large amounts of data.
A search form (GET)
Each field's name becomes a key in $_GET. Values are always strings — or missing entirely if the form hasn't been submitted:
<?php $products = ['Desk lamp', 'Standing desk', 'Desk chair', 'Bookshelf', 'Lamp shade']; $q = trim($_GET['q'] ?? ''); $results = $q === '' ? [] : array_filter($products, fn($p) => stripos($p, $q) !== false); ?> <form method="get" action="search.php"> <input type="search" name="q" value="<?= htmlspecialchars($q) ?>"> <button>Search</button> </form> <?php if ($q !== ''): ?> <p><?= count($results) ?> result(s) for "<?= htmlspecialchars($q) ?>"</p> <ul> <?php foreach ($results as $r): ?> <li><?= htmlspecialchars($r) ?></li> <?php endforeach; ?> </ul> <?php endif; ?>
GET /search.php GET /search.php?q=desk
> GET /search.php <form method="get" action="search.php"> <input type="search" name="q" value=""> <button>Search</button> </form> > GET /search.php?q=desk <form method="get" action="search.php"> <input type="search" name="q" value="desk"> <button>Search</button> </form> <p>3 result(s) for "desk"</p> <ul> <li>Desk lamp</li> <li>Standing desk</li> <li>Desk chair</li> </ul>
Putting the search term back into the input's value keeps it visible after searching. Every value that came from the user is passed through htmlspecialchars before being output — the next lesson explains why that matters so much.
A contact form (POST)
A common pattern is a single script that shows the form on GET and processes it on POST. $_SERVER['REQUEST_METHOD'] tells you which:
<?php $name = ''; $message = ''; $sent = false; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $name = trim($_POST['name'] ?? ''); $message = trim($_POST['message'] ?? ''); if ($name !== '' && $message !== '') { // a real site would save the message or email it here $sent = true; } } ?> <?php if ($sent): ?> <p>Thanks, <?= htmlspecialchars($name) ?>! We got your <?= strlen($message) ?>-character message.</p> <?php else: ?> <form method="post"> <input name="name" value="<?= htmlspecialchars($name) ?>"> <textarea name="message"><?= htmlspecialchars($message) ?></textarea> <button>Send</button> </form> <?php endif; ?>
GET /contact.php POST /contact.php name=Ana&message= POST /contact.php name=Ana&message=Hello+there!
> GET /contact.php <form method="post"> <input name="name" value=""> <textarea name="message"></textarea> <button>Send</button> </form> > POST /contact.php <form method="post"> <input name="name" value="Ana"> <textarea name="message"></textarea> <button>Send</button> </form> > POST /contact.php <p>Thanks, Ana! We got your 12-character message.</p>
When the message is missing, the form is shown again with the name the user already typed, so they don't have to start over. A form without an action submits to the same URL.
Checkboxes, selects and arrays
Unchecked checkboxes aren't sent at all, so check with isset. A field name ending in [] makes PHP collect several values into an array:
<?php header('Content-Type: text/plain'); $size = $_POST['size'] ?? 'none'; $extras = $_POST['extras'] ?? []; $giftWrap = isset($_POST['gift']); echo "Size: $size\n"; echo "Extras: ", $extras === [] ? "none" : implode(", ", $extras), "\n"; echo "Gift wrap: ", $giftWrap ? "yes" : "no", "\n";
POST /order.php size=M&extras[]=milk&extras[]=sugar&gift=on POST /order.php size=L
> POST /order.php Size: M Extras: milk, sugar Gift wrap: yes > POST /order.php Size: L Extras: none Gift wrap: no
The HTML for those fields would be <select name="size">, <input type="checkbox" name="extras[]" value="milk"> and <input type="checkbox" name="gift">. Never trust that a value is one you offered: a user can send anything, so check it against the allowed options (lesson 47).
Post/Redirect/Get
After a successful POST, if you show a page directly and the user refreshes, the browser offers to send the form again — and you might create a duplicate order. The fix is to redirect after a successful POST. The browser then loads the result page with a GET, which is safe to refresh:
<?php if ($_SERVER['REQUEST_METHOD'] === 'POST') { $email = trim($_POST['email'] ?? ''); if (filter_var($email, FILTER_VALIDATE_EMAIL)) { // save the sign-up here, then: header('Location: /signup.php?done=1'); exit; } $error = 'Please enter a valid email address.'; } ?> <?php if (isset($_GET['done'])): ?> <p>You're on the list!</p> <?php else: ?> <?php if (isset($error)): ?><p class="error"><?= $error ?></p><?php endif; ?> <form method="post"><input name="email"><button>Join</button></form> <?php endif; ?>
POST /signup.php email=nope POST /signup.php email=ana@example.com GET /signup.php?done=1
> POST /signup.php <p class="error">Please enter a valid email address.</p><form method="post"><input name="email"><button>Join</button></form> > POST /signup.php < 302 Found < Location: /signup.php?done=1 > GET /signup.php?done=1 <p>You're on the list!</p>
Always call exit after a redirect header, or the rest of the script keeps running. Lesson 48 shows how to carry a "success" message across the redirect with a session.
Reading the raw body
$_POST is only filled for form encodings. APIs that receive JSON read the raw request body instead:
<?php $data = json_decode(file_get_contents('php://input'), true);
Try it yourself
Build a tip calculator form: the bill and the tip percentage are submitted with GET, and the result is shown below the form.