PHP Forms

Process user input safely: read $_GET/$_POST, validate with filter_var, escape output, handle file uploads, and protect against CSRF attacks.

Intermediate 10 min read 9 examples

$_GET vs $_POST

AspectGETPOST
Data locationURL query stringRequest body
Visible in URLYesNo
CacheableYesNo
BookmarkableYesNo
Max size~2KB (URL limit)Usually 8MB+ (post_max_size)
Use forReads, searches, filtersWrites, logins, forms

Reading Form Data Safely

PHP
<?php
// Detect HTTP method
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
    showForm();
    exit;
}

// Always default missing fields - prevents "undefined index" warnings
$name    = trim($_POST["name"]    ?? "");
$email   = trim($_POST["email"]   ?? "");
$message = trim($_POST["message"] ?? "");

// Read URL query (GET)
$page    = (int) ($_GET["page"] ?? 1);
$search  = trim($_GET["q"] ?? "");

Validation with filter_var

PHP
<?php
$errors = [];

if ($name === "") {
    $errors["name"] = "Name is required";
} elseif (mb_strlen($name) > 100) {
    $errors["name"] = "Name too long";
}

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $errors["email"] = "Invalid email address";
}

$url = filter_var($_POST["website"] ?? "", FILTER_VALIDATE_URL);
if ($url === false) {
    $errors["website"] = "Invalid URL";
}

$age = filter_var($_POST["age"] ?? "", FILTER_VALIDATE_INT, [
    "options" => ["min_range" => 13, "max_range" => 120],
]);
if ($age === false) {
    $errors["age"] = "Age must be between 13 and 120";
}

if (!empty($errors)) {
    showForm($errors);
    exit;
}

Sanitization & Escaping

PHP
<?php
// HTML output - escape with htmlspecialchars
echo "Hello, " . htmlspecialchars($name, ENT_QUOTES, "UTF-8");

// URL building - urlencode
header("Location: /search?q=" . urlencode($search));

// SQL - NEVER concatenate. Use prepared statements:
$stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
$stmt->execute([$name, $email]);

// JSON output - json_encode handles escaping
header("Content-Type: application/json");
echo json_encode(["name" => $name]);
Escape per context

Different output contexts need different escaping: HTML = htmlspecialchars(), HTML attribute = same plus ENT_QUOTES, URL = urlencode(), JS string = json_encode(), SQL = prepared statements. One-size-fits-all escaping is unsafe.

Full Contact Form Example

PHPcontact.php
<?php
session_start();

$errors = [];
$values = ["name" => "", "email" => "", "message" => ""];
$saved = false;

if ($_SERVER["REQUEST_METHOD"] === "POST") {
    // 1. Read with defaults
    $values["name"]    = trim($_POST["name"]    ?? "");
    $values["email"]   = trim($_POST["email"]   ?? "");
    $values["message"] = trim($_POST["message"] ?? "");

    // 2. CSRF check
    if (!hash_equals($_SESSION["csrf"] ?? "", $_POST["csrf"] ?? "")) {
        $errors["form"] = "Invalid form token. Please try again.";
    }

    // 3. Validate
    if ($values["name"] === "")  $errors["name"] = "Name required";
    if (!filter_var($values["email"], FILTER_VALIDATE_EMAIL))
        $errors["email"] = "Invalid email";
    if (mb_strlen($values["message"]) < 10)
        $errors["message"] = "Message too short";

    // 4. Process
    if (!$errors) {
        // ... save to DB or send email
        $saved = true;
        $values = ["name" => "", "email" => "", "message" => ""];
    }
}

// Always generate a fresh token
$_SESSION["csrf"] = bin2hex(random_bytes(32));
?>
<form method="post">
    <input type="hidden" name="csrf" value="<?= htmlspecialchars($_SESSION["csrf"]) ?>">

    <input name="name" value="<?= htmlspecialchars($values["name"]) ?>">
    <?php if (isset($errors["name"])): ?><small><?= $errors["name"] ?></small><?php endif; ?>

    <input name="email" value="<?= htmlspecialchars($values["email"]) ?>">
    <textarea name="message"><?= htmlspecialchars($values["message"]) ?></textarea>

    <button>Send</button>
</form>

File Uploads

PHP
<?php
// HTML: <form enctype="multipart/form-data" method="post">
//         <input type="file" name="avatar">
//       </form>

if (!isset($_FILES["avatar"]) || $_FILES["avatar"]["error"] !== UPLOAD_ERR_OK) {
    die("Upload failed");
}

$file = $_FILES["avatar"];

// Check size (also enforce in php.ini upload_max_filesize)
if ($file["size"] > 2 * 1024 * 1024) die("File too large (max 2MB)");

// Verify MIME type by content, NOT by filename
$mime = mime_content_type($file["tmp_name"]);
$allowed = ["image/jpeg" => "jpg", "image/png" => "png", "image/webp" => "webp"];
if (!isset($allowed[$mime])) die("Only JPG, PNG, WebP allowed");

// Generate safe filename - never trust user-supplied name
$ext = $allowed[$mime];
$name = bin2hex(random_bytes(16)) . ".$ext";
$dest = __DIR__ . "/uploads/$name";

if (!move_uploaded_file($file["tmp_name"], $dest)) {
    die("Failed to save upload");
}

CSRF Protection

PHP
<?php
session_start();

function csrfToken(): string {
    if (empty($_SESSION["csrf"])) {
        $_SESSION["csrf"] = bin2hex(random_bytes(32));
    }
    return $_SESSION["csrf"];
}

function csrfCheck(): void {
    $posted = $_POST["csrf"] ?? "";
    $stored = $_SESSION["csrf"] ?? "";
    // Use hash_equals to prevent timing attacks
    if (!hash_equals($stored, $posted)) {
        http_response_code(403);
        die("CSRF token mismatch");
    }
}

// In the form
?>
<form method="post">
    <input type="hidden" name="csrf" value="<?= csrfToken() ?>">
    ...
</form>

// In the handler
<?php csrfCheck(); ?>

Next Steps

Frequently Asked Questions

Use GET for safe, idempotent reads (search, filters) - URL is shareable and cacheable. Use POST for actions that change state (create, update, delete). Never put passwords in GET - they appear in browser history and server logs.

Use filter_var($email, FILTER_VALIDATE_EMAIL). It returns the email if valid or false if not. This implements the actual RFC and catches almost all bad addresses. Regex-only solutions are notoriously buggy.

Order: trim whitespace, validate format (reject bad input early), then escape on output. Don't alter the user's data permanently with sanitization - validate as-is, escape per output context.

Without it, an attacker can craft a webpage that submits forms to your site using the victim's logged-in cookies. CSRF tokens verify the request actually came from your form, not a third party.

Generate it on form display, store it in the session, embed it as a hidden field. On submit, compare the posted token to the session value with hash_equals(). If they differ, reject the request.