PHP Email Validation

Published Updated

In modern PHP, start with the standard validation filter. It checks the shape of an email address before the rest of the form handler touches it. Email validation is not a place to prove your regex muscles.

Then decide what your application really needs next: a confirmation link, a unique-account rule, a delivery attempt, or a mailing-list consent step. Those checks answer different questions, and the code stays easier to review when each one has its own boundary.

This beginner tutorial keeps the scope narrow on purpose. If you are validating a whole registration form, read form and spelling validation in PHP after this. If the accepted address needs a message, continue to sending email in PHP.

Validate the Address Shape

Trim the incoming value, require it to be present, and let PHP do the syntax check:

<?php

function validateEmailAddress(string $rawEmail): array
{
    $email = trim($rawEmail);
    $errors = [];

    if ($email === "") {
        $errors[] = "Enter an email address.";
    } elseif (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
        $errors[] = "Enter a valid email address.";
    }

    return [$email, $errors];
}

That is enough for the first boundary. You know whether the submitted string has the shape of an email address that PHP recognizes. You do not know whether the mailbox exists, whether the user controls it, whether your mail provider will deliver to it, or whether the user wants product mail at that address. Keeping those differences clear prevents a small signup form from quietly turning syntax validation into a promise the application cannot make.

Use the Function in a Form Handler

A form handler can validate the email address before it creates an account or stores a newsletter signup:

<?php

if ($_SERVER["REQUEST_METHOD"] === "POST") {
    [$email, $errors] = validateEmailAddress($_POST["email"] ?? "");

    if ($errors !== []) {
        require __DIR__ . "/../views/signup.php";
        return;
    }

    saveSignup(db(), $email);

    header("Location: /check-your-email", true, 303);
    return;
}

require __DIR__ . "/../views/signup.php";

The 303 redirect keeps the browser from resubmitting the form on refresh. That little pattern is still worth using for signups, password resets, profile updates, and any other POST request that changes state.

Store Through a Prepared Statement

Validation does not replace the database boundary. Store the accepted address with a prepared statement:

<?php

function saveSignup(PDO $pdo, string $email): void
{
    $statement = $pdo->prepare(
        "INSERT INTO newsletter_signups (email, created_at)
         VALUES (:email, CURRENT_TIMESTAMP)",
    );

    $statement->execute(["email" => $email]);
}

If the address must be unique, make that a database constraint too:

CREATE TABLE newsletter_signups (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    email TEXT NOT NULL UNIQUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

The PHP validation gives the user a clear error. The database constraint protects the fact after the PHP request is gone.

Confirm Ownership Separately

An email address can be syntactically valid and still belong to someone else. For accounts, password resets, and mailing lists, send a confirmation link before trusting the address:

<?php

function createConfirmationToken(): string
{
    return bin2hex(random_bytes(32));
}

Store a hash of the token, send the raw token in the confirmation URL, and expire it. The validation step says "this looks like an email address." The confirmation step says "the user can receive mail at this address right now." Treat those as separate promises in the code, especially for account creation, password resets, and anything that could expose private data to the wrong inbox.

Avoid the Old Regex Trap

Regular expressions are still useful for application-specific rules, such as a company-only allowlist. They are a poor default for general email syntax:

<?php

function isCompanyEmail(string $email): bool
{
    return str_ends_with(strtolower($email), "@example.com");
}

That function answers a product question: is this address in the allowed domain? It does not try to replace PHP's email syntax filter.

The job is to keep junk strings out of a form, and the shortest route is still the plainest: validate shape with the standard filter, store through prepared statements, add database constraints for facts that must stay true, and confirm ownership when the product depends on it.

For the larger form workflow, move next to form and spelling validation in PHP. For delivery, read sending email in PHP.

This example has been updated from the original CodeWalkers email-validation tutorial, which was built around a hand-written regular expression. FILTER_VALIDATE_EMAIL accepts valid addresses those patterns reject, including plus-addressing and newer top-level domains, which is why the filter opens this page instead of a pattern.

Frequently Asked Questions

Should an email address be lowercased before storing it?

The domain, certainly, since domains are case-insensitive. Most applications lowercase the whole address for consistent matching. The part before the at sign is technically case-sensitive, though almost no provider treats it that way.

Does the filter accept plus-addressed emails?

Yes. Plus-addressing is valid and FILTER_VALIDATE_EMAIL accepts it. Hand-written patterns that strip or reject the plus sign break a legitimate address people actively use to tag their own mail.

Is there a maximum length for an email address?

254 characters for the whole address. A column sized well below that, or one that truncates silently rather than rejecting an oversized value, is an easy bug to miss because it only shows up on unusual addresses.

Does the filter reject addresses with a subdomain?

No. A subdomain is an ordinary part of a valid domain, so an address at mail.example.com passes exactly as one at example.com does, however many labels the domain carries.

Does validation prove the domain exists?

No. FILTER_VALIDATE_EMAIL is a pure syntax check with no network access and no DNS lookup, so a well-formed address at a domain nobody registered still passes. Only delivery proves a mailbox is real.

Sources

  1. [1]
  2. [2]
  3. [3]
    PHP trim
    (php.net)
  4. [4]
    Input Validation Cheat Sheet
    (cheatsheetseries.owasp.org)