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 search.php
<?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; ?>
HTTP
GET /search.php
GET /search.php?q=desk
Responses
> 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 contact.php
<?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; ?>
HTTP
GET /contact.php
POST /contact.php name=Ana&message=
POST /contact.php name=Ana&message=Hello+there!
Responses
> 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 order.php
<?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";
HTTP
POST /order.php size=M&extras[]=milk&extras[]=sugar&gift=on
POST /order.php size=L
Responses
> 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 signup.php
<?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; ?>
HTTP
POST /signup.php email=nope
POST /signup.php email=ana@example.com
GET /signup.php?done=1
Responses
> 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
<?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.

Try it yourself · tip.php
Edit the code, copy it into tip.php, then start PHP's built-in web server with php -S localhost:8000 in that folder and open http://localhost:8000/tip.php (PHP 8.5). How to install PHP