Skip to content

PHP Forms — Form Handling, Validation, Security, and File Uploads

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about PHP Forms. We cover key concepts, practical examples, and best practices to help you master this topic.

PHP forms accept user input through HTML form elements, process it server-side with validation and sanitization, and return feedback while preventing common attacks like XSS, CSRF, and SQL injection.

Why It Matters

Forms are how users interact with your application. A contact form, registration page, search box, checkout process -- every user-facing feature involves form handling. Poorly handled forms lead to bad user experience, data loss, and security vulnerabilities. Understanding form validation, error display, and data persistence across submissions is essential for building professional web applications.

Real-World Use

WordPress contact forms handle millions of submissions daily with spam filtering and file attachments. E-commerce checkout forms validate credit cards and shipping addresses. Login forms authenticate users and manage sessions. Laravel's form validation handles complex rules like "required_if" and "unique:users,email". DodaTech's support portal uses validated forms for ticket submission with file attachments.

What You Will Learn

  • Creating HTML forms that submit to PHP
  • GET vs POST methods and when to use each
  • Form validation: required fields, email format, numeric range
  • Displaying validation errors and preserving form state
  • CSRF protection for form submissions
  • Secure file upload handling
  • Preventing XSS and SQL injection in form data

Learning Path

flowchart LR
  A[Superglobals] --> B[Forms
You are here] B --> C[Sessions] C --> D[Cookies] D --> E[Headers] style B fill:#f90,color:#fff

Basic Form Structure

An HTML form that submits to PHP:

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

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

    <button type="submit">Submit</button>
</form>

The method="post" attribute tells the browser to send data in the request body. The action attribute specifies which PHP file receives the data. If empty or omitted, the form submits to the current page, which is the common pattern for self-processing forms.

Self-Processing Form Pattern

A single file that both displays the form and processes it:

<?php
$name = $email = '';
$errors = [];
$success = false;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = trim($_POST['name'] ?? '');
    $email = trim($_POST['email'] ?? '');

    if (empty($name)) {
        $errors['name'] = 'Name is required';
    }
    if (empty($email)) {
        $errors['email'] = 'Email is required';
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors['email'] = 'Invalid email format';
    }

    if (empty($errors)) {
        $success = true;
        // Process the form (save to database, send email, etc.)
    }
}
?>

<!DOCTYPE html>
<html>
<body>
    <?php if ($success): ?>
        <p>Thank you, <?= htmlspecialchars($name) ?>! Form submitted.</p>
    <?php else: ?>
        <form method="post">
            <div>
                <label>Name:</label>
                <input type="text" name="name"
                       value="<?= htmlspecialchars($name) ?>">
                <?php if (isset($errors['name'])): ?>
                    <span class="error"><?= $errors['name'] ?></span>
                <?php endif; ?>
            </div>
            <div>
                <label>Email:</label>
                <input type="email" name="email"
                       value="<?= htmlspecialchars($email) ?>">
                <?php if (isset($errors['email'])): ?>
                    <span class="error"><?= $errors['email'] ?></span>
                <?php endif; ?>
            </div>
            <button type="submit">Submit</button>
        </form>
    <?php endif; ?>
</body>
</html>

The form preserves user input using value="<?= htmlspecialchars($name) ?>". This way, if validation fails, the user does not have to retype everything.

GET vs POST

Aspect GET POST
Data location URL query string Request body
Visibility Visible in URL Not visible
Bookmarkable Yes No
Size limit ~2000 characters No practical limit
Security Low (data in URL) Medium (body not cached)
Use case Searches, filters Login, forms, mutations

Validation Techniques

Required Fields

if (empty($_POST['username'])) {
    $errors['username'] = 'Username is required';
}

Email Validation

$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
if ($email === false) {
    $errors['email'] = 'Invalid email address';
}

Numeric Range

$age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT, [
    'options' => ['min_range' => 1, 'max_range' => 120]
]);
if ($age === false) {
    $errors['age'] = 'Age must be between 1 and 120';
}

String Length

$message = trim($_POST['message'] ?? '');
if (strlen($message) < 10) {
    $errors['message'] = 'Message must be at least 10 characters';
}
if (strlen($message) > 1000) {
    $errors['message'] = 'Message must not exceed 1000 characters';
}

URL Validation

$website = filter_input(INPUT_POST, 'website', FILTER_VALIDATE_URL);
if ($website !== false && $website !== null) {
    // Valid URL provided
}

CSRF Protection

Cross-Site Request Forgery (CSRF) tricks users into submitting forms on your site from another site. Prevent it with tokens:

<?php
session_start();

// Generate token on form display
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['csrf_token'];

// Validate on submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $submittedToken = $_POST['csrf_token'] ?? '';
    if (!hash_equals($token, $submittedToken)) {
        die('Invalid CSRF token');
    }
    // Process form
}
?>

<form method="post">
    <input type="hidden" name="csrf_token" value="<?= $token ?>">
    <!-- other fields -->
    <button type="submit">Submit</button>
</form>

File Upload Handling

Files require special handling with the enctype attribute:

<form method="post" enctype="multipart/form-data">
    <input type="file" name="document" accept=".pdf,.jpg,.png">
    <button type="submit">Upload</button>
</form>

PHP processing:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['document'])) {
    $file = $_FILES['document'];
    $errors = [];

    // Check for upload errors
    if ($file['error'] !== UPLOAD_ERR_OK) {
        $errors[] = 'Upload failed with code: ' . $file['error'];
    }

    // Validate file size (5MB max)
    $maxSize = 5 * 1024 * 1024;
    if ($file['size'] > $maxSize) {
        $errors[] = 'File too large. Maximum is 5MB.';
    }

    // Validate file type by examining content
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mimeType = finfo_file($finfo, $file['tmp_name']);
    finfo_close($finfo);

    $allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
    if (!in_array($mimeType, $allowedTypes)) {
        $errors[] = 'Only JPG, PNG, and PDF files are allowed.';
    }

    if (empty($errors)) {
        $safeName = bin2hex(random_bytes(16)) . '.' .
                    pathinfo($file['name'], PATHINFO_EXTENSION);
        $dest = 'uploads/' . $safeName;
        move_uploaded_file($file['tmp_name'], $dest);
        echo "File uploaded successfully: $safeName";
    } else {
        foreach ($errors as $error) {
            echo "<p>$error</p>";
        }
    }
}

Security Rules for File Uploads

  1. Never trust the $_FILES['type'] -- it is sent by the client
  2. Use finfo to detect the real file type
  3. Generate a random filename, never use the original name
  4. Store files outside the web root when possible
  5. Limit file size both in PHP and in server configuration

Common Mistakes

1. Not Validating Server-Side

Client-side validation (HTML5 required, JavaScript) is easily bypassed. Always validate on the server.

2. Outputting User Input Without Escaping

echo "Welcome, " . $_POST['name'];  // XSS vulnerability

Always use htmlspecialchars() or a template engine that auto-escapes.

3. Not Checking Request Method

if (isset($_POST['submit'])) {  // Can be spoofed

Check $_SERVER['REQUEST_METHOD'] === 'POST' instead.

4. Using Extract on Superglobals

extract($_POST);  // Creates variables $name, $email - dangerous!

Variables may overwrite existing ones. Access superglobals directly.

5. Ignoring File Upload Errors

if ($_FILES['file']['error'] === UPLOAD_ERR_INI_SIZE) {
    // File exceeds upload_max_filesize in php.ini
}

Always check $file['error'] !== UPLOAD_ERR_OK before processing.

Practice Questions

  1. What is the difference between GET and POST methods? GET sends data in the URL, is bookmarkable, has size limits. POST sends data in the body, has no size limit, and is not cached.

  2. Why should you escape output from form submissions? To prevent XSS Attacks. The browser executes injected HTML/JavaScript if you output raw user input.

  3. What does enctype="multipart/form-data" do? It tells the browser to encode the form for file uploads. Without it, files are not sent.

  4. How do you prevent CSRF Attacks on forms? Use a unique token stored in the session, include it as a hidden field, and validate on submission.

  5. Challenge: Build a complete registration form with name, email, password, password confirmation, age, and profile picture upload. Implement all validations and CSRF protection.

Mini Project: Contact Form with File Attachment

Build a secure contact form:

<?php
session_start();
$errors = [];
$success = false;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $token = $_POST['csrf_token'] ?? '';
    if (!hash_equals($_SESSION['csrf_token'] ?? '', $token)) {
        die('Invalid token');
    }

    $name = trim($_POST['name'] ?? '');
    $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
    $subject = trim($_POST['subject'] ?? '');
    $message = trim($_POST['message'] ?? '');

    if (empty($name)) $errors[] = 'Name is required';
    if ($email === false) $errors[] = 'Valid email required';
    if (strlen($subject) < 3) $errors[] = 'Subject too short';
    if (strlen($message) < 10) $errors[] = 'Message too short';

    $attachment = '';
    if (isset($_FILES['attachment']) && $_FILES['attachment']['error'] === UPLOAD_ERR_OK) {
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mime = finfo_file($finfo, $_FILES['attachment']['tmp_name']);
        finfo_close($finfo);
        if (in_array($mime, ['image/jpeg', 'image/png', 'application/pdf'])) {
            $attachment = 'uploads/' . bin2hex(random_bytes(8));
            move_uploaded_file($_FILES['attachment']['tmp_name'], $attachment);
        } else {
            $errors[] = 'Invalid file type';
        }
    }

    if (empty($errors)) {
        file_put_contents('contacts.log', implode('|', [
            date('Y-m-d H:i:s'), $name, $email, $subject, $message, $attachment
        ]) . "\n", FILE_APPEND);
        $success = true;
    }
}

$_SESSION['csrf_token'] = $_SESSION['csrf_token'] ?? bin2hex(random_bytes(32));
?>

FAQ

{{< faq "How do I preserve form input after submission?" "Set the value attribute to the submitted value: value="". This shows what the user typed." >}}

What is the maximum file upload size?

Controlled by upload_max_filesize and post_max_size in php.ini. Default is usually 2MB to 8MB. Change with ini_set() or .htaccess.

{{< faq "How do I handle multiple file uploads?" "Use array notation in the name: name=\"files[]\". Multiple file inputs with the same name create an array. Multiple attribute on input type=file also works." >}} {{< faq "Why does my form submit as GET even with method=post?" "Check for missing or malformed form tags. A form with method=\"post\" but action containing query parameters may append them." >}}
Should I validate on the client, server, or both?

Both. Client-side validation provides instant feedback. Server-side validation is the security boundary. Never rely on client-side alone.

What is Next

Now that you can handle forms, proceed to Sessions to learn how to persist user state across page requests. Then read Cookies to understand client-side data storage.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro