beginner22 min·php

PHP Form Handling

Learn PHP form handling with GET, POST, validation, and security best practices. Master form submission, data sanitization, error handling, and file uploads with real examples.

Forms are the backbone of any interactive web application. Every time a user logs in, submits a search query, fills out a contact form, or uploads a file, PHP is processing that form data on the server. Mastering PHP form handling is not optional — it is a fundamental skill every PHP developer must have.

In this complete guide, you will learn how to handle form submissions using GET and POST, validate and sanitize user input, protect your forms from security vulnerabilities, handle file uploads, and connect forms to a MySQL database. Every code example is ready to run in our compiler.

GET vs POST: Choosing the Right Method

HTML forms use two HTTP methods to send data to the server: GET and POST. Understanding the difference is the first step in proper PHP form handling.

GET Method

The GET method appends form data to the URL as query parameters. Data is visible in the address bar and has a size limit of approximately 2048 characters.

<!-- HTML Form using GET -->
<form method="GET" action="process.php">
    <input type="text" name="search" placeholder="Search...">
    <button type="submit">Search</button>
</form>
// process.php - Handling GET data
<?php
if (isset($_GET['search'])) {
    $search = $_GET['search'];
    echo "You searched for: " . htmlspecialchars($search);
}
?>

When to use GET:

  • Search forms
  • Filtering and sorting
  • Pagination links
  • Any form where the result can be bookmarked or shared

POST Method

The POST method sends form data in the HTTP request body. Data is not visible in the URL and has no practical size limit.

<!-- HTML Form using POST -->
<form method="POST" action="process.php">
    <input type="text" name="username" placeholder="Username">
    <input type="password" name="password" placeholder="Password">
    <button type="submit">Login</button>
</form>
// process.php - Handling POST data
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = $_POST['username'];
    $password = $_POST['password'];
    echo "Welcome, " . htmlspecialchars($username);
}
?>

When to use POST:

  • Login and registration forms
  • Contact forms
  • File uploads
  • Any form that changes data on the server
  • Forms with sensitive information
Feature GET POST
Data visibility Visible in URL Hidden in request body
Size limit ~2048 characters No practical limit
Bookmarkable Yes No
Security Less secure More secure
Use case Search, filtering Login, registration, uploads

Handling Form Data with PHP Superglobals

PHP provides three superglobals for accessing form data: $_GET, $_POST, and $_REQUEST.

$_GET

Accesses data submitted via the GET method:

<?php
if (isset($_GET['name'])) {
    $name = $_GET['name'];
    echo "Hello, " . htmlspecialchars($name);
}
?>

$_POST

Accesses data submitted via the POST method:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $email = $_POST['email'] ?? '';
    $message = $_POST['message'] ?? '';
    
    echo "Email: " . htmlspecialchars($email);
    echo "Message: " . htmlspecialchars($message);
}
?>

$_REQUEST

Contains data from both GET, POST, and cookies. Generally not recommended because it can lead to security issues:

<?php
// Avoid $_REQUEST in production — it mixes GET, POST, and cookie data
// Use $_GET or $_POST explicitly instead
?>

The Null Coalescing Operator

Always use the null coalescing operator (??) or isset() to check if form fields exist before using them. This prevents PHP warnings and undefined index errors:

<?php
$name = $_POST['name'] ?? 'Guest';
$email = $_POST['email'] ?? '';
$age = $_POST['age'] ?? 0;

echo "Name: " . htmlspecialchars($name) . "\n";
echo "Email: " . htmlspecialchars($email) . "\n";
echo "Age: $age\n";
?>

Form Validation in PHP

Never trust user input. Always validate and sanitize data before processing it. PHP provides several built-in functions for validation.

Required Fields

Check that required fields are not empty:

<?php
$errors = [];

$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');

if (empty($name)) {
    $errors[] = "Name is required.";
}

if (empty($email)) {
    $errors[] = "Email is required.";
}

if (empty($errors)) {
    echo "Form is valid! Processing...\n";
    echo "Name: " . htmlspecialchars($name) . "\n";
    echo "Email: " . htmlspecialchars($email) . "\n";
} else {
    echo "Validation errors:\n";
    foreach ($errors as $error) {
        echo "- $error\n";
    }
}
?>

Email Validation

Use PHP’s filter_var() function for reliable email validation:

<?php
$email = $_POST['email'] ?? '';

if (empty($email)) {
    echo "Email is required.\n";
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    echo "Invalid email format.\n";
} else {
    echo "Valid email: " . htmlspecialchars($email) . "\n";
}
?>

Number Validation

Validate numeric input with type checking and range validation:

<?php
$age = $_POST['age'] ?? '';

if (empty($age)) {
    echo "Age is required.\n";
} elseif (!is_numeric($age)) {
    echo "Age must be a number.\n";
} elseif ($age < 1 || $age > 150) {
    echo "Age must be between 1 and 150.\n";
} else {
    echo "Valid age: $age\n";
}
?>

String Length Validation

Enforce minimum and maximum length for text fields:

<?php
$username = $_POST['username'] ?? '';

if (strlen($username) < 3) {
    echo "Username must be at least 3 characters.\n";
} elseif (strlen($username) > 50) {
    echo "Username must be at most 50 characters.\n";
} elseif (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
    echo "Username can only contain letters, numbers, and underscores.\n";
} else {
    echo "Valid username: " . htmlspecialchars($username) . "\n";
}
?>

Custom Validation with filter_input_array

PHP’s filter_input_array() lets you validate multiple fields at once:

<?php
$filters = [
    'name'    => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
    'email'   => FILTER_VALIDATE_EMAIL,
    'age'     => FILTER_VALIDATE_INT,
    'website' => FILTER_VALIDATE_URL,
];

$data = filter_input_array(INPUT_POST, $filters);

if ($data) {
    $name    = $data['name'] ?? '';
    $email   = $data['email'] ?? false;
    $age     = $data['age'] ?? false;
    $website = $data['website'] ?? false;

    echo "Name: " . htmlspecialchars($name ?? '') . "\n";
    echo "Email: " . ($email ? htmlspecialchars($email) : 'Invalid') . "\n";
    echo "Age: " . ($age !== false ? $age : 'Invalid') . "\n";
    echo "Website: " . ($website ? htmlspecialchars($website) : 'Invalid') . "\n";
}
?>

Form Security Best Practices

Security is not optional. Every form is an attack vector if handled improperly. Follow these practices to protect your application.

Data Sanitization

Always sanitize user input before displaying or storing it:

<?php
$raw_input = $_POST['bio'] ?? '';

// Remove HTML tags
$clean = strip_tags($raw_input);

// Encode special characters for safe HTML output
$safe_for_html = htmlspecialchars($raw_input, ENT_QUOTES, 'UTF-8');

// Remove extra whitespace
$trimmed = trim(preg_replace('/\s+/', ' ', $raw_input));

echo "Original: $raw_input\n";
echo "Cleaned: $clean\n";
echo "Safe HTML: $safe_html\n";
echo "Trimmed: $trimmed\n";
?>

Preventing SQL Injection

Never concatenate user input directly into SQL queries. Always use prepared statements with PDO:

<?php
// WRONG — vulnerable to SQL injection
// $query = "SELECT * FROM users WHERE email = '$email'";

// CORRECT — use prepared statements
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'user', 'pass');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$email = $_POST['email'] ?? '';

$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$stmt->execute(['email' => $email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

if ($user) {
    echo "User found: " . htmlspecialchars($user['name'] ?? 'Unknown') . "\n";
} else {
    echo "No user found with that email.\n";
}
?>

CSRF Protection

Cross-Site Request Forgery (CSRF) attacks trick users into submitting forms without their knowledge. Always use CSRF tokens:

<?php
session_start();

// Generate CSRF token (place this in your form page)
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// In your HTML form:
// <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">

// Validate CSRF token on form submission (place this in your processing page)
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $token = $_POST['csrf_token'] ?? '';
    
    if (!hash_equals($_SESSION['csrf_token'], $token)) {
        die("CSRF token validation failed.");
    }
    
    // Token is valid — process the form
    echo "CSRF token validated. Processing form...\n";
    
    // Regenerate token after successful validation
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
?>

XSS Prevention

Cross-Site Scripting (XSS) attacks inject malicious scripts into your pages. Always escape output:

<?php
$user_input = $_POST['comment'] ?? '';

// Always use htmlspecialchars() when outputting to HTML
echo "<div class='comment'>" . htmlspecialchars($user_input, ENT_QUOTES, 'UTF-8') . "</div>";

// For JavaScript context, use json_encode()
echo "<script>var userComment = " . json_encode($user_input) . ";</script>";
?>

Error Handling in Forms

Good error handling improves user experience and makes debugging easier.

Displaying Validation Errors

Show errors next to the relevant fields:

<?php
$errors = [];
$form_data = [];

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

    if (empty($form_data['name'])) {
        $errors['name'] = "Name is required.";
    } elseif (strlen($form_data['name']) < 2) {
        $errors['name'] = "Name must be at least 2 characters.";
    }

    if (empty($form_data['email'])) {
        $errors['email'] = "Email is required.";
    } elseif (!filter_var($form_data['email'], FILTER_VALIDATE_EMAIL)) {
        $errors['email'] = "Please enter a valid email.";
    }

    if (empty($errors)) {
        echo "Form submitted successfully!\n";
        echo "Name: " . htmlspecialchars($form_data['name']) . "\n";
        echo "Email: " . htmlspecialchars($form_data['email']) . "\n";
    } else {
        echo "Please fix the following errors:\n";
        foreach ($errors as $field => $error) {
            echo "- $field: $error\n";
        }
    }
}
?>

Redirect on Success

After successful form processing, redirect to a thank-you page to prevent form resubmission:

<?php
// After successful processing
// header('Location: thank-you.php');
// exit();

echo "Redirect would happen here after successful submission.\n";
?>

File Upload with PHP Forms

File uploads require special handling for both the HTML form and PHP processing.

HTML Form for File Upload

<form method="POST" action="upload.php" enctype="multipart/form-data">
    <input type="file" name="avatar" accept="image/*">
    <button type="submit">Upload</button>
</form>

PHP File Upload Processing

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['avatar'])) {
    $file = $_FILES['avatar'];
    
    // Check for upload errors
    if ($file['error'] !== UPLOAD_ERR_OK) {
        echo "Upload error: " . $file['error'] . "\n";
    } else {
        // Validate file type
        $allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mime_type = $finfo->file($file['tmp_name']);
        
        if (!in_array($mime_type, $allowed_types)) {
            echo "Invalid file type. Allowed: JPEG, PNG, GIF, WebP\n";
        } else {
            // Validate file size (5MB max)
            if ($file['size'] > 5 * 1024 * 1024) {
                echo "File too large. Maximum size is 5MB.\n";
            } else {
                // Generate safe filename
                $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
                $safe_name = bin2hex(random_bytes(16)) . '.' . $extension;
                $upload_dir = 'uploads/';
                
                // Create directory if it doesn't exist
                if (!is_dir($upload_dir)) {
                    mkdir($upload_dir, 0755, true);
                }
                
                $destination = $upload_dir . $safe_name;
                
                if (move_uploaded_file($file['tmp_name'], $destination)) {
                    echo "File uploaded successfully: $destination\n";
                } else {
                    echo "Failed to move uploaded file.\n";
                }
            }
        }
    }
}
?>

Key File Upload Security Rules

  1. Always validate the MIME type using finfo — never trust the file extension alone
  2. Set a reasonable file size limit
  3. Generate random filenames to prevent overwriting existing files
  4. Store uploaded files outside the web root when possible
  5. Set proper directory permissions (755 for directories, 644 for files)

Connecting Forms to MySQL

Most forms need to persist data in a database. Here is a complete example using PDO:

<?php
// Database connection
$pdo = new PDO(
    'mysql:host=localhost;dbname=myapp',
    'username',
    'password',
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);

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

    // Server-side validation
    $errors = [];
    if (empty($name)) $errors[] = "Name is required.";
    if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = "Valid email is required.";
    }
    if (empty($message)) $errors[] = "Message is required.";

    if (empty($errors)) {
        // Insert using prepared statement
        $stmt = $pdo->prepare(
            "INSERT INTO contacts (name, email, message, created_at) 
             VALUES (:name, :email, :message, NOW())"
        );
        $stmt->execute([
            'name'    => $name,
            'email'   => $email,
            'message' => $message,
        ]);

        echo "Message saved! ID: " . $pdo->lastInsertId() . "\n";
    } else {
        echo "Validation failed:\n";
        foreach ($errors as $e) {
            echo "- $e\n";
        }
    }
}

// Retrieve and display all messages
$stmt = $pdo->query("SELECT * FROM contacts ORDER BY created_at DESC LIMIT 5");
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);

echo "\nRecent messages:\n";
foreach ($messages as $msg) {
    echo "[$msg[created_at]] " . htmlspecialchars($msg['name']) . ": " . htmlspecialchars($msg['message']) . "\n";
}
?>

Complete Form Example

Here is a full contact form that combines all the techniques covered in this tutorial:

<?php
session_start();

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

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

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Validate CSRF
    if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
        die("Invalid request.");
    }

    // Sanitize input
    $form['name']    = trim($_POST['name'] ?? '');
    $form['email']   = trim($_POST['email'] ?? '');
    $form['subject'] = trim($_POST['subject'] ?? '');
    $form['message'] = trim($_POST['message'] ?? '');

    // Validate
    if (strlen($form['name']) < 2) {
        $errors['name'] = "Name must be at least 2 characters.";
    }
    if (!filter_var($form['email'], FILTER_VALIDATE_EMAIL)) {
        $errors['email'] = "Please enter a valid email.";
    }
    if (empty($form['subject'])) {
        $errors['subject'] = "Subject is required.";
    }
    if (strlen($form['message']) < 10) {
        $errors['message'] = "Message must be at least 10 characters.";
    }

    if (empty($errors)) {
        // Process form (send email, save to database, etc.)
        echo "Message sent successfully!\n";
        echo "Name: " . htmlspecialchars($form['name']) . "\n";
        echo "Email: " . htmlspecialchars($form['email']) . "\n";
        echo "Subject: " . htmlspecialchars($form['subject']) . "\n";
        echo "Message: " . htmlspecialchars($form['message']) . "\n";

        // Reset form
        $form = ['name' => '', 'email' => '', 'subject' => '', 'message' => ''];
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    } else {
        echo "Please fix the errors:\n";
        foreach ($errors as $field => $error) {
            echo "- $error\n";
        }
    }
}
?>

PHP Form Handling Best Practices

  1. Always validate server-side — Client-side validation improves UX but can be bypassed. Server-side validation is mandatory.
  2. Use prepared statements — Never concatenate user input into SQL queries. Always use PDO prepared statements.
  3. Sanitize all output — Use htmlspecialchars() when outputting user data to HTML. Use json_encode() for JavaScript contexts.
  4. Implement CSRF protection — Every form that changes data should include a CSRF token.
  5. Use HTTPS — Form data should always be transmitted over HTTPS to prevent interception.
  6. Validate on the server, confirm on the client — Do validation twice: once for UX (client) and once for security (server).
  7. Set proper file upload restrictions — Validate file types by MIME, enforce size limits, and use random filenames.
  8. Handle errors gracefully — Show user-friendly error messages while logging technical details for debugging.

Try running every PHP code example in our compiler. Modify the validation rules, change the input data, and see how PHP handles each scenario. Hands-on practice is the fastest way to build muscle memory for form handling.

Frequently Asked Questions

What is the difference between $_GET and $_POST in PHP?

$_GET retrieves data submitted via the GET method, where form values appear as URL query parameters (visible in the address bar, limited to ~2048 characters). $_POST retrieves data submitted via the POST method, where values are sent in the HTTP request body (not visible in the URL, no practical size limit). Use GET for search forms and filtering; use POST for login, registration, and any form that changes server data.

How do I validate user input in PHP?

Use PHP’s built-in validation functions: filter_var() with FILTER_VALIDATE_EMAIL for emails, FILTER_VALIDATE_INT for integers, and FILTER_VALIDATE_URL for URLs. For string validation, use strlen() for length, preg_match() for patterns, and empty() for required fields. Always validate on the server side — client-side validation can be bypassed.

How do I prevent SQL injection in PHP forms?

Never concatenate user input directly into SQL queries. Use PDO prepared statements with parameterized queries:

$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$stmt->execute(['email' => $email]);

This ensures the database treats user input as data, not as executable SQL code.

What is CSRF protection and how do I implement it?

CSRF (Cross-Site Request Forgery) attacks trick authenticated users into submitting unintended forms. To prevent it: generate a random token with bin2hex(random_bytes(32)), store it in $_SESSION, include it as a hidden field in your form, and validate it on submission using hash_equals(). Regenerate the token after each successful submission.

How do I handle file uploads securely in PHP?

Key security steps: (1) Set enctype="multipart/form-data" on the HTML form. (2) Validate the MIME type using finfo — never trust the file extension alone. (3) Enforce file size limits. (4) Generate random filenames with bin2hex(random_bytes(16)). (5) Store uploads outside the web root when possible. (6) Set directory permissions to 755 and file permissions to 644.

How do I display form validation errors next to each field?

Store errors in an associative array keyed by field name ($errors['email'] = "Invalid email"). In your HTML form, check if each field has an error and display it in a <span> or <div> next to that field. Use CSS to style error messages (typically red text). Also repopulate the form fields with previously entered values so users do not have to re-enter everything.