Skip to content
Happy Programming Guide
Start learning
PHP

Working with Forms and Form Handling in PHP

How PHP receives form data, why you must never trust it, and the validation, escaping and redirect pattern that makes a form safe.

Lines of source code on a dark computer screen

A form sends data to the server; PHP reads it out of $_POST or $_GET. The reading part takes one line. Everything that matters is what you do next.

PHP
<?php
$name = $_POST["name"] ?? "";
?>

The form#

HTML
<form method="post" action="signup.php">
  <label for="name">Name</label>
  <input id="name" name="name" type="text" required>

  <label for="email">Email</label>
  <input id="email" name="email" type="email" required>

  <button type="submit">Sign up</button>
</form>

The name attribute is what PHP uses as the key. No name, no data — a field with only an id never arrives.

GET or POST?#

GET POST
Data goes in The URL The request body
Bookmarkable Yes No
Use for Searches, filters Anything that changes data

Rule of thumb: if submitting it twice would be a problem, use POST. Passwords and personal details must never travel in a URL, because URLs end up in browser history and server logs.

Reading the data safely#

PHP
<?php
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
    // Someone loaded the page normally — show the empty form.
    $name = $email = "";
    $errors = [];
} else {
    $name  = trim($_POST["name"]  ?? "");
    $email = trim($_POST["email"] ?? "");
    $errors = [];
}
?>

The ?? "" matters. Reading a key that was not submitted raises a warning and gives you null, and a missing field is completely normal — anyone can send a request without it.

Validating#

PHP
<?php
if ($name === "") {
    $errors["name"] = "Please enter your name.";
} elseif (mb_strlen($name) > 100) {
    $errors["name"] = "That name is too long.";
}

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $errors["email"] = "That does not look like an email address.";
}

if (!$errors) {
    // safe to use
}
?>

filter_var with FILTER_VALIDATE_EMAIL saves you writing a regular expression that will be wrong at the edges.

Escaping output#

When you print submitted data back — in an error message, a confirmation, a comment — escape it, or a submitted <script> tag will run in the next visitor’s browser.

PHP
<?php
function e(string $value): string {
    return htmlspecialchars($value, ENT_QUOTES, "UTF-8");
}
?>

<input name="name" value="<?= e($name) ?>">
<p>Thanks, <?= e($name) ?></p>

A short helper like e() makes it hard to forget. Escaping is about output, not input — store what the user typed, escape it when you display it.

Keeping the values on error#

Nothing is more annoying than a form that clears itself because one field was wrong. Print the submitted value back into each input:

HTML
<div class="field">
  <label for="email">Email</label>
  <input id="email" name="email" type="email" value="<?= e($email) ?>">
  <?php if (isset($errors["email"])): ?>
    <p class="error"><?= e($errors["email"]) ?></p>
  <?php endif; ?>
</div>

Post-redirect-get#

After a successful POST, redirect instead of printing the result directly. Otherwise refreshing the page resubmits the form, and people do refresh.

PHP
<?php
if (!$errors) {
    saveSignup($name, $email);

    header("Location: thanks.php");
    exit;                      // always exit after a redirect
}
?>

The exit is not optional — without it PHP carries on executing the rest of the script.

File uploads#

PHP
<form method="post" enctype="multipart/form-data">
  <input type="file" name="photo">
</form>
PHP
<?php
if ($_FILES["photo"]["error"] === UPLOAD_ERR_OK) {
    $tmp = $_FILES["photo"]["tmp_name"];

    // Check the real type, never the submitted filename.
    $type = mime_content_type($tmp);
    $allowed = ["image/jpeg" => "jpg", "image/png" => "png"];

    if (isset($allowed[$type])) {
        $name = bin2hex(random_bytes(8)) . "." . $allowed[$type];
        move_uploaded_file($tmp, __DIR__ . "/uploads/" . $name);
    }
}
?>

Two rules: never use the uploaded filename as-is, and never decide the file type from its extension. Both are how attackers get a script into an uploads folder.

Questions people ask#

What is a CSRF token?

A random value stored in the session and included as a hidden field, checked on submit. It stops another site submitting your form on a logged-in user’s behalf. Any form that changes data should have one.

Should I use filter_input instead of $_POST?

filter_input(INPUT_POST, "email", FILTER_VALIDATE_EMAIL) reads and validates in one step, which is tidy. Reading with ?? and validating separately is easier to follow while learning.

Why is my form data empty?

Check the name attributes exist, the method matches what you are reading, and the action points at the right file. Print var_dump($_POST) at the top to see exactly what arrived.

Do I still need server validation with JavaScript validation?

Yes, always. JavaScript validation is for the user’s convenience and can be bypassed entirely.

Where to go next#

Next lessonPHP security best practices

Keep reading

Keep going — pick your next guide

The fastest way to improve is to read one guide, then build the thing it describes. Start with the basics, or jump straight to a project.

Ask a question or share what worked

Your email address will not be published. Required fields are marked *