Lesson 10 of 12

PHP Forms: GET and POST

Forms are how visitors talk to your website: search boxes, contact forms, sign-up pages and checkout screens. In the HTML Forms lesson you built the front end. Now you will write the PHP that receives the data, checks it and responds. Handling PHP forms is also where most security mistakes happen, so this lesson treats safety as part of the basics, not an extra.

How form data reaches PHP

When a form is submitted, the browser sends each field as a name/value pair. The key is the field’s name attribute, so a field without a name is never sent. The form’s method attribute decides how the data travels, and its action attribute decides which PHP file receives it.

PHP collects the data into two built-in associative arrays (called superglobals, because they are available everywhere): $_GET for data in the URL and $_POST for data in the request body. Everything in them arrives as strings (or arrays of strings), even when the user typed a number.

GET or POST?

GETPOST
Where the data goesIn the URL: search.php?q=dosa&page=2In the body of the request, not visible in the URL
Bookmarkable and shareableYesNo
Stored in history and server logsYesNo (the URL only)
Typical useSearching, filtering, sorting, paginationLogging in, sign-ups, sending messages, anything that changes data

The rule of thumb for PHP forms: use GET when the request only reads something and the result could be bookmarked. Use POST when it changes something or contains private data such as passwords. Remember that POST is not encryption; only HTTPS protects the data on its way to the server.

Reading GET data safely

Never assume a field exists: a visitor can open your page without submitting the form, or edit the URL by hand. The null coalescing operator ?? from PHP Operators gives a default without a warning. Then, whenever you print user input into HTML, pass it through htmlspecialchars(). It converts characters such as <, >, & and quotes into HTML entities, so the browser displays them as text instead of running them. Skipping this step opens the door to cross-site scripting (XSS).

<?php
declare(strict_types=1);

// PHP fills $_GET from the URL. Here we set it by hand to show
// what PHP would receive for: search.php?q=<b>Dosa</b>&page=2
$_GET = ['q' => '<b>Dosa</b>', 'page' => '2'];

$query = trim($_GET['q'] ?? '');
$page  = filter_var($_GET['page'] ?? 1, FILTER_VALIDATE_INT, [
    'options' => ['min_range' => 1, 'default' => 1],
]);

if ($query === '') {
    echo "<p>Please type something to search.</p>";
} else {
    echo "<p>Results for " . htmlspecialchars($query) . " (page $page)</p>";
}
Output
<p>Results for <b>Dosa</b> (page 2)</p>

The visitor sees the text Results for <b>Dosa</b> rather than bold HTML, which proves the input was treated as plain text. filter_var() with FILTER_VALIDATE_INT turns the page number into a real integer and falls back to 1 if someone types page=abc or page=-5.

Handling a POST form with validation

A typical form page does three jobs: show the form, check the submitted data, and either show errors or act on the data. Check $_SERVER['REQUEST_METHOD'] to know whether the form was submitted. Validation means checking every field against clear rules on the server, even if the HTML form already has required or type="email". Browser checks are a convenience for honest users; anyone can bypass them.

<?php
declare(strict_types=1);

// Simulate a submitted form. On a real server PHP fills these for you.
$_SERVER['REQUEST_METHOD'] = 'POST';
$_POST = ['name' => '  Priya ', 'email' => 'priya@example', 'age' => '17', 'message' => ''];

/** @return array<string, string> field => error message */
function validateContact(array $input): array
{
    $errors = [];

    if (trim($input['name'] ?? '') === '') {
        $errors['name'] = 'Please enter your name.';
    }
    if (filter_var($input['email'] ?? '', FILTER_VALIDATE_EMAIL) === false) {
        $errors['email'] = 'Please enter a valid email address.';
    }
    $age = filter_var($input['age'] ?? '', FILTER_VALIDATE_INT,
        ['options' => ['min_range' => 18, 'max_range' => 120]]);
    if ($age === false) {
        $errors['age'] = 'You must be 18 or older.';
    }
    if (mb_strlen(trim($input['message'] ?? '')) < 10) {
        $errors['message'] = 'Message must be at least 10 characters.';
    }
    return $errors;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $errors = validateContact($_POST);

    if ($errors === []) {
        echo "Thank you, " . htmlspecialchars(trim($_POST['name'])) . "!";
    } else {
        foreach ($errors as $field => $error) {
            echo "$field: $error\n";
        }
    }
}
Output
email: Please enter a valid email address.
age: You must be 18 or older.
message: Message must be at least 10 characters.

Collecting errors in an array keyed by field name lets you show each message next to the right input. When you redisplay the form, fill each field with the submitted value, escaped, so users do not have to retype everything: value="<?= htmlspecialchars($_POST['name'] ?? '') ?>". This is called a sticky form.

Tip: After a successful POST, redirect with header('Location: /thank-you.php'); exit; instead of printing a page directly. This Post/Redirect/Get pattern stops the browser from resubmitting the form if the user refreshes.

Protecting PHP forms against CSRF

Cross-site request forgery (CSRF) tricks a logged-in user’s browser into submitting one of your forms from another website. The standard defence is a secret token: generate a random value, store it in the user’s session, put it in a hidden field, and on submission check that the two match.

<?php
declare(strict_types=1);
session_start();

// 1. Create a secret token once per session
$_SESSION['csrf_token'] ??= bin2hex(random_bytes(32));

// 2. On submit, check the token before doing anything else
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $sent = $_POST['csrf_token'] ?? '';
    if (!is_string($sent) || !hash_equals($_SESSION['csrf_token'], $sent)) {
        http_response_code(403);
        exit('Invalid form submission.');
    }
    // token is valid: continue with validation as in contact.php
}
?>
<form method="post" action="contact.php">
  <input type="hidden" name="csrf_token"
         value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
  <!-- other fields -->
  <button type="submit">Send</button>
</form>
Output
Example output (first visit, a GET request): the form HTML, including a hidden field such as <input type="hidden" name="csrf_token" value="9f2c…e41a"> that holds a 64-character random hex token. A POST request without the matching token stops with HTTP status 403 and the text: Invalid form submission.

random_bytes() produces cryptographically secure randomness and hash_equals() compares strings in constant time, so attackers cannot guess the token by measuring response times. Frameworks such as Laravel and Symfony, and WordPress with its nonces, handle this for you, but it helps to understand what they do.

Watch out: In PHP forms, validation, escaping and CSRF tokens solve different problems. Validation checks that data makes sense, htmlspecialchars() protects HTML output, and CSRF tokens prove the request came from your own form. When the data goes into a database you also need prepared statements, covered in PHP MySQL with PDO.

Further reading

Key takeaways

  • Form fields reach PHP through their name attribute, in $_GET (URL) or $_POST (request body), always as strings.
  • Use GET for reading and bookmarkable searches; use POST for anything that changes data or is private.
  • Read fields with ?? defaults and validate every field on the server, for example with filter_var().
  • Escape every user value printed into HTML with htmlspecialchars() to prevent XSS.
  • Protect state-changing forms with a CSRF token checked by hash_equals(), and redirect after a successful POST.

Quick quiz

Check what you learned – pick an answer.

1. Which method should a login form use?

2. A user types alert(1) into your search box. What should you do before printing it on the results page?

3. Why is server-side validation needed when the HTML form already uses required and type="email"?

Practice exercise

Write feedback.php that handles a POST form with two fields: name (required) and rating (an integer from 1 to 5). If both are valid, print "Thanks NAME, you rated us RATING/5"; otherwise print each error. Escape the name in the output.

Show a hint

Use trim() for the name and filter_var() with FILTER_VALIDATE_INT and min_range/max_range for the rating.

Show the solution
<?php
declare(strict_types=1);

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = trim($_POST['name'] ?? '');
    $rating = filter_var($_POST['rating'] ?? '', FILTER_VALIDATE_INT,
        ['options' => ['min_range' => 1, 'max_range' => 5]]);

    $errors = [];
    if ($name === '') {
        $errors[] = 'Name is required.';
    }
    if ($rating === false) {
        $errors[] = 'Rating must be a whole number from 1 to 5.';
    }

    if ($errors === []) {
        echo 'Thanks ' . htmlspecialchars($name) . ", you rated us $rating/5";
    } else {
        foreach ($errors as $error) {
            echo '<p>' . $error . '</p>';
        }
    }
}
?>
<form method="post">
  <label>Name <input name="name" required></label>
  <label>Rating <input name="rating" type="number" min="1" max="5" required></label>
  <button type="submit">Send</button>
</form>

Progress is saved in this browser only.