Home PHP PHP Forms and User Input Explained — GET, POST and Validation

PHP Forms and User Input Explained — GET, POST and Validation

In Plain English 🔥
Imagine a paper form at the doctor's office — you fill in your name, date of birth, and symptoms, then hand it to the receptionist who reads it and does something with it. A PHP form works exactly the same way: the HTML page is the paper form, the user fills it in, and when they hit Submit, PHP is the receptionist on the other side who reads every field and decides what to do next. Without this mechanism, websites could only show you information — they could never take any from you.
⚡ Quick Answer
Imagine a paper form at the doctor's office — you fill in your name, date of birth, and symptoms, then hand it to the receptionist who reads it and does something with it. A PHP form works exactly the same way: the HTML page is the paper form, the user fills it in, and when they hit Submit, PHP is the receptionist on the other side who reads every field and decides what to do next. Without this mechanism, websites could only show you information — they could never take any from you.

Almost every useful thing on the web involves a form. Logging into Instagram, searching on Google, buying something on Amazon, leaving a comment — all of it starts with a user typing something and hitting a button. If you want to build anything interactive with PHP, understanding how forms work is not optional, it is the very foundation everything else sits on.

Before PHP (and server-side languages like it), web pages were just static documents — like a poster on a wall. You could look at them but not talk back. PHP solved this by giving the server the ability to receive data from the browser, process it, and respond dynamically. That two-way conversation between the browser and the server is what makes the modern web feel alive.

By the end of this article you will know how to build an HTML form, send its data to a PHP script using both GET and POST methods, read and display that data safely, validate it so bad input gets rejected, and understand the security pitfalls every beginner trips over. You will have working, runnable code you can drop straight into your own project.

How a Form Actually Sends Data to PHP — The Full Picture

Before writing a single line of PHP, you need to understand the journey data takes from the browser to your script. When a user fills out a form and clicks Submit, the browser packages up every field into a request and sends it to the URL specified in the form's action attribute. The method attribute decides HOW that data travels — either stuck onto the URL (GET) or tucked inside the request body (POST).

Think of GET like writing a note on the outside of an envelope — anyone who sees the envelope can read it, and the note becomes part of the address. POST is like putting the note inside a sealed envelope — it still gets delivered, but it is not visible on the outside.

On the PHP side, the language automatically unpacks that envelope for you and stores every field in a special array called a superglobal. If the form used GET, your data lands in $_GET. If it used POST, it lands in $_POST. You do not have to do anything special to make this happen — PHP does it automatically on every single request. Your job is to reach into those arrays and use the values responsibly.

contact_form.php · PHP
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
<?php
// ─────────────────────────────────────────────
// contact_form.php
// A single file that shows the form AND handles
// the submission — this pattern is called a
// 'self-processing form' and is very common.
// ─────────────────────────────────────────────

// Check whether the form has actually been submitted.
// $_SERVER['REQUEST_METHOD'] tells us HOW this page was requested.
// On first load it is 'GET' (just visiting the page).
// After the user clicks Submit it becomes 'POST'.
$formWasSubmitted = ($_SERVER['REQUEST_METHOD'] === 'POST');

$userName    = '';   // Will hold the cleaned name value
$userMessage = '';   // Will hold the cleaned message value
$feedbackToUser = ''; // What we show the user after submission

if ($formWasSubmitted) {
    // htmlspecialchars() converts dangerous characters like < > & into
    // safe display versions. This stops basic XSS attacks.
    // FILTER_DEFAULT trims nothing — we handle that manually.
    $userName    = htmlspecialchars(trim($_POST['name']));
    $userMessage = htmlspecialchars(trim($_POST['message']));

    // trim() removes accidental spaces at the start and end.
    // Without it, '  Alice  ' and 'Alice' would be treated differently.

    $feedbackToUser = "Thanks, $userName! We received your message.";
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Contact Us</title>
</head>
<body>

    <h1>Contact Us</h1>

    <?php if ($feedbackToUser !== ''): ?>
        <!-- Only render this block when there IS a message to show -->
        <p style="color: green;"><?= $feedbackToUser ?></p>
    <?php endif; ?>

    <!-- action="" means 'send back to THIS same file' -->
    <!-- method="post" means data goes in the request body, not the URL -->
    <form action="" method="post">

        <label for="name">Your Name:</label><br>
        <!-- The 'name' attribute on each input is the KEY in $_POST -->
        <input type="text" id="name" name="name"
               value="<?= htmlspecialchars($userName) ?>">
        <br><br>

        <label for="message">Your Message:</label><br>
        <textarea id="message" name="message" rows="4" cols="40"
        ><?= htmlspecialchars($userMessage) ?></textarea>
        <br><br>

        <button type="submit">Send Message</button>
    </form>

</body>
</html>
▶ Output
── First visit (no submission) ──────────────────
Shows the blank form. No feedback message.

── After filling in Name: Alice, Message: Hello! ──
Thanks, Alice! We received your message.
[Form re-displays with previous values still filled in]
🔥
Why One File?Using a single file for both the form and its handler (action="") is perfectly valid and very common for small forms. For large apps you would split these into separate files, but for learning, keeping everything together lets you see the full flow in one place.

GET vs POST — Choosing the Right Method Every Time

This is one of those decisions that matters more than it looks. GET and POST are not just two ways to do the same thing — they are designed for fundamentally different situations, and picking the wrong one causes real problems.

GET appends form data to the URL as a query string, like /search.php?query=shoes&size=10. This is perfect for searches and filters because the URL is now shareable and bookmarkable. Hit refresh and nothing bad happens — you are just re-running the same search. GET requests are also cached by browsers, which can speed things up.

POST sends data invisibly in the request body. Use POST whenever you are changing something — logging in, submitting a comment, placing an order, updating a profile. If you used GET for a login form, the password would appear in the URL, in browser history, and in server logs. That is a serious security issue. POST also avoids the 'double submission' problem: most browsers warn you before resubmitting a POST request, which prevents accidental duplicate orders.

The rule of thumb: GET is for asking questions (reading data). POST is for taking action (writing or changing data).

search_with_get.php · PHP
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
<?php
// ─────────────────────────────────────────────
// search_with_get.php
// Demonstrates GET — ideal for a search form
// because the results URL can be bookmarked.
// e.g. /search_with_get.php?keyword=laptop&category=electronics
// ─────────────────────────────────────────────

// isset() checks that the key actually EXISTS in $_GET.
// Without this check, accessing $_GET['keyword'] on a fresh
// page load causes an 'Undefined index' notice.
$searchKeyword = isset($_GET['keyword'])
    ? htmlspecialchars(trim($_GET['keyword']))
    : '';

$selectedCategory = isset($_GET['category'])
    ? htmlspecialchars(trim($_GET['category']))
    : 'all';

// Simulate a product list (in a real app this comes from a database)
$allProducts = [
    ['name' => 'Laptop Pro 15',   'category' => 'electronics'],
    ['name' => 'Wireless Mouse',  'category' => 'electronics'],
    ['name' => 'Running Shoes',   'category' => 'footwear'],
    ['name' => 'Leather Boots',   'category' => 'footwear'],
];

// Filter products based on the search input
$matchingProducts = array_filter($allProducts, function($product) use ($searchKeyword, $selectedCategory) {
    $nameMatches     = ($searchKeyword === '' || stripos($product['name'], $searchKeyword) !== false);
    $categoryMatches = ($selectedCategory === 'all' || $product['category'] === $selectedCategory);
    return $nameMatches && $categoryMatches;
});
?>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Product Search</title></head>
<body>

<h1>Search Products</h1>

<!-- method="get" — results URL becomes shareable/bookmarkable -->
<form action="" method="get">

    <label for="keyword">Search:</label>
    <!-- value= re-fills the box after submission so user sees what they typed -->
    <input type="text" id="keyword" name="keyword"
           value="<?= $searchKeyword ?>" placeholder="e.g. laptop">

    <label for="category">Category:</label>
    <select id="category" name="category">
        <option value="all"      <?= $selectedCategory === 'all'         ? 'selected' : '' ?>>All</option>
        <option value="electronics" <?= $selectedCategory === 'electronics' ? 'selected' : '' ?>>Electronics</option>
        <option value="footwear"    <?= $selectedCategory === 'footwear'    ? 'selected' : '' ?>>Footwear</option>
    </select>

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

<hr>
<h2>Results</h2>

<?php if (empty($matchingProducts)): ?>
    <p>No products found. Try a different search.</p>
<?php else: ?>
    <ul>
        <?php foreach ($matchingProducts as $product): ?>
            <!-- Each result is safely echoed — already sanitised above -->
            <li><?= $product['name'] ?> <em>(<?= $product['category'] ?>)</em></li>
        <?php endforeach; ?>
    </ul>
<?php endif; ?>

</body>
</html>
▶ Output
── URL after searching 'shoe' in All categories ──
/search_with_get.php?keyword=shoe&category=all

Results:
• Running Shoes (footwear)

── URL after searching '' in electronics ──
/search_with_get.php?keyword=&category=electronics

Results:
• Laptop Pro 15 (electronics)
• Wireless Mouse (electronics)
⚠️
Watch Out: Never Use GET for Passwords or Sensitive DataIf your login form uses method="get", the password appears in the URL bar, gets saved in browser history, and lands in your web server's access logs in plain text. Always use POST for login, registration, payment, and any form that handles sensitive information.

Validating User Input — Never Trust What the Browser Sends

Here is the most important mindset shift in all of web development: treat every piece of data from a form as potentially hostile until you have checked it yourself. Users make typos. Some users are malicious. Either way, your PHP script has to decide what counts as valid input and reject everything that does not meet that standard.

Validation happens on two levels. Client-side validation (HTML required, type="email", etc.) gives users instant feedback without a page reload — great for user experience. But it is trivially bypassed: anyone can open browser dev tools and remove the required attribute, or send a raw HTTP request with no browser at all. Server-side validation in PHP is the real gatekeeper, and it is non-negotiable.

For each field, ask yourself three questions: Is it present? Is it the right type/format? Is it within acceptable limits? PHP gives you powerful tools for this: empty() to catch blank values, filter_var() to validate emails and URLs, strlen() for length checks, and preg_match() for pattern matching. Doing these checks consistently is what separates a toy project from a production-ready application.

registration_form.php · PHP
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
<?php
// ─────────────────────────────────────────────
// registration_form.php
// Full server-side validation example.
// Shows how to collect errors and re-display
// the form with helpful messages.
// ─────────────────────────────────────────────

$formWasSubmitted = ($_SERVER['REQUEST_METHOD'] === 'POST');

// Store all validation error messages here.
// Key = field name, Value = error string.
$validationErrors = [];

// Keep old input so the form re-fills after a failed submission.
// The user should NOT have to retype everything just because one
// field was wrong — that is a terrible user experience.
$oldInput = [
    'username' => '',
    'email'    => '',
    'age'      => '',
];

if ($formWasSubmitted) {

    // ── Collect raw values ──────────────────────────────
    // We store raw (unsanitised) values in $oldInput so we
    // can re-fill the form. We sanitise later, after validation.
    $rawUsername = trim($_POST['username'] ?? '');
    $rawEmail    = trim($_POST['email']    ?? '');
    $rawAge      = trim($_POST['age']      ?? '');

    $oldInput = [
        'username' => htmlspecialchars($rawUsername),
        'email'    => htmlspecialchars($rawEmail),
        'age'      => htmlspecialchars($rawAge),
    ];

    // ── Validate: Username ──────────────────────────────
    if (empty($rawUsername)) {
        $validationErrors['username'] = 'Username is required.';
    } elseif (strlen($rawUsername) < 3 || strlen($rawUsername) > 20) {
        $validationErrors['username'] = 'Username must be 3–20 characters.';
    } elseif (!preg_match('/^[a-zA-Z0-9_]+$/', $rawUsername)) {
        // Only letters, numbers, and underscores allowed
        $validationErrors['username'] = 'Username can only contain letters, numbers and underscores.';
    }

    // ── Validate: Email ─────────────────────────────────
    if (empty($rawEmail)) {
        $validationErrors['email'] = 'Email address is required.';
    } elseif (!filter_var($rawEmail, FILTER_VALIDATE_EMAIL)) {
        // FILTER_VALIDATE_EMAIL returns false if the format is invalid
        $validationErrors['email'] = 'Please enter a valid email address.';
    }

    // ── Validate: Age ───────────────────────────────────
    if (empty($rawAge)) {
        $validationErrors['age'] = 'Age is required.';
    } elseif (!ctype_digit($rawAge)) {
        // ctype_digit() returns true ONLY if every character is 0-9
        // This rejects '25.5', '-1', '25abc' etc.
        $validationErrors['age'] = 'Age must be a whole number.';
    } elseif ((int)$rawAge < 13 || (int)$rawAge > 120) {
        $validationErrors['age'] = 'Age must be between 13 and 120.';
    }

    // ── Only proceed if zero errors ─────────────────────
    if (empty($validationErrors)) {
        // At this point data is valid. Safe to use.
        $cleanUsername = htmlspecialchars($rawUsername);
        $cleanEmail    = filter_var($rawEmail, FILTER_SANITIZE_EMAIL);
        $cleanAge      = (int)$rawAge;

        // In a real app: save to database, send welcome email, etc.
        // For now, just show a success message.
        echo "<!DOCTYPE html><html><body>";
        echo "<h1>Registration Successful!</h1>";
        echo "<p>Welcome, <strong>$cleanUsername</strong>! ";
        echo "We sent a confirmation to $cleanEmail.</p>";
        echo "</body></html>";
        exit; // Stop further output — do not show the form again
    }
}
?>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Register</title>
<style>
    .error { color: red; font-size: 0.9em; }
    label  { display: block; margin-top: 12px; font-weight: bold; }
    input  { padding: 6px; width: 250px; }
</style>
</head>
<body>

<h1>Create an Account</h1>

<form action="" method="post" novalidate>
    <!-- novalidate disables browser validation so PHP is the sole gatekeeper -->

    <label for="username">Username</label>
    <input type="text" id="username" name="username"
           value="<?= $oldInput['username'] ?>">
    <!-- Show error only if this field has one -->
    <?php if (isset($validationErrors['username'])): ?>
        <span class="error"><?= $validationErrors['username'] ?></span>
    <?php endif; ?>

    <label for="email">Email Address</label>
    <input type="email" id="email" name="email"
           value="<?= $oldInput['email'] ?>">
    <?php if (isset($validationErrors['email'])): ?>
        <span class="error"><?= $validationErrors['email'] ?></span>
    <?php endif; ?>

    <label for="age">Age</label>
    <input type="number" id="age" name="age"
           value="<?= $oldInput['age'] ?>">
    <?php if (isset($validationErrors['age'])): ?>
        <span class="error"><?= $validationErrors['age'] ?></span>
    <?php endif; ?>

    <br><br>
    <button type="submit">Register</button>
</form>

</body>
</html>
▶ Output
── Submitting: username='Al', email='notanemail', age='abc' ──
[Form re-displays with all three fields re-filled]

Username → Username must be 3–20 characters.
Email → Please enter a valid email address.
Age → Age must be a whole number.

── Submitting: username='Alice_99', email='alice@example.com', age='28' ──
Registration Successful!
Welcome, Alice_99! We sent a confirmation to alice@example.com.
⚠️
Pro Tip: Validate First, Sanitise SecondAlways validate before you sanitise. Sanitising changes the data (e.g. stripping characters), which can mask whether the original input was actually valid. Validate the raw input, reject it if it fails, then sanitise whatever passes before you store or display it.
Feature / AspectGET MethodPOST Method
Data locationAppended to the URL (?key=value)Sent in the request body, not visible in URL
Bookmarkable / ShareableYes — URL captures the full stateNo — data is not in the URL
Browser back/refreshSafe — just re-runs the same requestBrowser warns before re-submitting
Data size limit~2,000 characters (URL length limit)Effectively unlimited (server config dependent)
Security for sensitive dataPoor — visible in URL, logs, historyBetter — not stored in URL or browser history
Caching by browser/proxyYes — responses can be cachedNo — POST responses are not cached
PHP superglobal used$_GET$_POST
Typical use caseSearch forms, filters, paginationLogin, registration, payments, file uploads
Idempotent (safe to repeat)?Yes — repeating has no side effectsNo — repeating could create duplicate records

🎯 Key Takeaways

  • PHP automatically parses submitted form data into $_GET or $_POST — you choose which one by setting method='get' or method='post' on the HTML form element.
  • Use GET for read-only operations (search, filter) because the URL is shareable and repeatable. Use POST for any action that writes, updates, or deletes data — and always for passwords.
  • Never echo $_GET or $_POST values directly into HTML — always wrap them in htmlspecialchars() first. Skipping this one step is the root cause of most XSS vulnerabilities in beginner PHP apps.
  • Server-side validation is non-negotiable. HTML required attributes and input types are a UX tool only — any user or bot can bypass them entirely. PHP must independently verify every value before trusting it.

⚠ Common Mistakes to Avoid

  • Mistake 1: Echoing $_POST or $_GET directly without sanitising — Symptom: A user types into a name field and it executes in the browser for anyone who views that page (XSS attack) — Fix: Always wrap output in htmlspecialchars() before echoing to the page: echo htmlspecialchars($_POST['name']). This converts < and > into safe < and > so the browser displays the text instead of executing it.
  • Mistake 2: Not checking isset() before accessing a superglobal key — Symptom: PHP throws 'Undefined index: email in form.php on line 12' on the very first page load before the form is submitted — Fix: Use the null coalescing operator ($_POST['email'] ?? '') or wrap access in isset($_POST['email']) checks. The ?? '' pattern is cleaner and returns an empty string as a default when the key does not exist.
  • Mistake 3: Trusting client-side validation as the only protection — Symptom: HTML required attributes and type="email" look like they are working in the browser, but a user (or attacker) sends a raw HTTP POST request with curl or Postman, bypassing all of it, submitting empty or malformed data that crashes your script or pollutes your database — Fix: Treat HTML validation as a UX convenience only. Every field must also be validated in PHP on the server side before you use or store the value. The browser is not your security layer.

Interview Questions on This Topic

  • QWhat is the difference between $_GET and $_POST in PHP, and how do you decide which one to use for a given form?
  • QA user submits a form with their name as . Your PHP script echoes it back to the page. What happens, and how do you fix it?
  • QIf you refresh a page after submitting a POST form, the browser asks 'Are you sure you want to resubmit?' — why does this happen, and what is the standard way to prevent the same form from being processed twice?

Frequently Asked Questions

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

$_GET holds data sent via the URL query string (e.g. page.php?name=Alice), making it visible and bookmarkable — ideal for searches. $_POST holds data sent in the HTTP request body, keeping it out of the URL — required for passwords, login forms, and anything that modifies data. Both are superglobal arrays PHP populates automatically on every request.

How do I stop PHP from showing 'Undefined index' notices when a form field is empty?

Use the null coalescing operator: $value = $_POST['fieldname'] ?? ''. This returns the value if it exists, or an empty string if it does not, without throwing any notice. Alternatively, check with isset($_POST['fieldname']) before accessing the key. This is especially important on the first page load before the form has been submitted.

Is HTML form validation (required, type='email') enough to protect my PHP application?

No — HTML validation is browser-side only and can be completely bypassed by disabling JavaScript, using browser dev tools, or sending a raw HTTP request with tools like curl or Postman. It improves user experience but provides zero security. Every field must also be validated inside your PHP script on the server before you use or store the data.

🔥
TheCodeForge Editorial Team Verified Author

Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.

← PreviousPHP Strings and String FunctionsNext →PHP Sessions and Cookies
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged