Handling forms safely

Read GET and POST input, validate it, show helpful errors, and escape output to prevent XSS.

  • Beginner
  • 5 min read
  • Updated 17 Oct 2026

What you will learn

  • Read input from $_GET and $_POST
  • Validate and normalise user input
  • Escape output with htmlspecialchars to prevent XSS
  • Use the Post/Redirect/Get pattern

Before you start

  • Functions
  • Basic HTML forms: form, input, label and button elements

Forms are how visitors send data to your site: searches, sign-ups, comments, orders. Handling them is where most security problems begin, so this lesson builds the safe habits from the start.

Treat every piece of input as untrusted. It can be missing, the wrong type, far too long, or deliberately malicious, even if your HTML form "only allows" certain values. Anyone can send any request to your server.

GET and POST

MethodData travels inUse for
GETThe URL: search.php?q=arraysSearches, filters, anything safe to bookmark
POSTThe request bodyAnything that changes data: sign-ups, orders, messages

PHP puts the data in two arrays, $_GET and $_POST. Values are always strings (or arrays for multi-value fields), never ints or bools.

PHP
<?php

// search.php?q=php+arrays
$query = trim($_GET['q'] ?? '');

echo 'You searched for: ' . htmlspecialchars($query, ENT_QUOTES, 'UTF-8');

?? '' avoids a warning when the parameter is missing.

Escape output: preventing XSS

Imagine a visitor searches for this:

Plain text
<script>alert('hacked')</script>

If you echo it directly, the browser runs that script. An attacker can send someone a link that steals their session or rewrites the page. This is cross-site scripting (XSS), one of the most common web vulnerabilities.

htmlspecialchars() turns special characters into harmless entities, so the browser shows them as text:

PHP
<?php

function e(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}

echo e("<script>alert('hacked')</script>");
// <script>alert('hacked')</script>

Escape at the moment of output, every time you put data into HTML. A tiny helper like e() makes it easy enough that you never skip it. Validation does not replace escaping.

A complete contact form

Save this as contact.php and run it with php -S localhost:8000.

PHP
<?php

declare(strict_types=1);

function e(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}

$errors = [];
$values = ['name' => '', 'email' => '', 'message' => ''];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 1. Read and normalise.
    $values['name'] = trim((string) ($_POST['name'] ?? ''));
    $values['email'] = trim((string) ($_POST['email'] ?? ''));
    $values['message'] = trim((string) ($_POST['message'] ?? ''));

    // 2. Validate.
    if ($values['name'] === '') {
        $errors['name'] = 'Enter your name.';
    } elseif (mb_strlen($values['name']) > 100) {
        $errors['name'] = 'Use 100 characters or fewer.';
    }

    if (!filter_var($values['email'], FILTER_VALIDATE_EMAIL)) {
        $errors['email'] = 'Enter an email address like name@example.com.';
    }

    if (mb_strlen($values['message']) < 10) {
        $errors['message'] = 'Write at least 10 characters.';
    }

    // 3. Act, then redirect.
    if ($errors === []) {
        // Save to a database or send an email here.
        header('Location: contact.php?sent=1', true, 303);
        exit;
    }
}
?>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"><title>Contact</title></head>
<body>
<?php if (isset($_GET['sent'])): ?>
    <p role="status">Thanks, your message was sent.</p>
<?php endif; ?>

<form method="post" novalidate>
    <p>
        <label for="name">Name</label>
        <input id="name" name="name" value="<?= e($values['name']) ?>" required>
        <?php if (isset($errors['name'])): ?><span><?= e($errors['name']) ?></span><?php endif; ?>
    </p>
    <p>
        <label for="email">Email</label>
        <input id="email" name="email" type="email" value="<?= e($values['email']) ?>" required>
        <?php if (isset($errors['email'])): ?><span><?= e($errors['email']) ?></span><?php endif; ?>
    </p>
    <p>
        <label for="message">Message</label>
        <textarea id="message" name="message" required><?= e($values['message']) ?></textarea>
        <?php if (isset($errors['message'])): ?><span><?= e($errors['message']) ?></span><?php endif; ?>
    </p>
    <button type="submit">Send</button>
</form>
</body>
</html>

Walk through what it does:

  1. Normalise: trim() removes accidental spaces; the (string) cast guards against someone sending an array instead of a string.
  2. Validate on the server: HTML's required and type="email" help honest users, but anyone can bypass them, so PHP checks again. filter_var(..., FILTER_VALIDATE_EMAIL) checks the email format.
  3. Keep the user's answers: after an error, each field is refilled with the escaped value, so nothing has to be retyped.
  4. Error messages say how to fix the problem, not just that something is wrong.
  5. Post/Redirect/Get: after success, header('Location: ...') with status 303 sends the browser to a normal GET page. Refreshing it will not resubmit the form.

header() must be called before any output, including blank lines or spaces before <?php. Otherwise PHP reports "headers already sent".

What this example does not cover yet

Real forms also need:

  • CSRF protection: a hidden secret token that proves the form came from your own site.
  • Prepared statements when saving to a database, to prevent SQL injection.
  • Rate limiting or a CAPTCHA to slow down spam.

Frameworks and WordPress provide these for you; the PHP and MySQL course covers prepared statements in depth.

Summary

  • $_GET and $_POST hold input as strings; read them with ?? '' and normalise with trim().
  • Never trust input: validate on the server even if the HTML form validates too.
  • Escape every value you output into HTML with htmlspecialchars($value, ENT_QUOTES, 'UTF-8').
  • Redisplay the form with the user's values and clear error messages.
  • After a successful POST, redirect with status 303.

Further reading