Form and Spelling Validation in PHP
Form input is not useful until the application knows what shape it is willing to accept.
The durable rule is boring: let the browser help the user, validate again on the server, let the database enforce facts that belong to the data model, and escape output when rendering HTML. Those are separate jobs, and blending them together is how form code becomes security code by accident, usually not very good security code.
This sits in the intermediate PHP tutorial track because the code crosses HTML, server-side branching, sessions, SQL constraints, and output escaping. If the server-side rules themselves are still fuzzy, read PHP conditionals and control flow and PHP email validation first. Most validation bugs are ordinary branches with the wrong comparison or a missing failure path.
The Split that Matters
A form submission crosses several boundaries, and each boundary owns a different decision:
- Browser hints help the user correct obvious mistakes.
- Server validation decides whether the request is acceptable.
- CSRF protection decides whether a state-changing request came through the right path.
- SQL constraints enforce facts that must remain true after the PHP request is gone.
- Output escaping makes accepted text safe in the output context where it appears.
Those jobs are related, but they are not interchangeable. required on an HTML field does not protect the server. A database UNIQUE constraint does not give the user a friendly form error by itself. htmlspecialchars() does not prove the email address is syntactically valid. A CSRF token does not make the submitted title a good title.
This is the distinction that keeps a small PHP app from turning into a pile of special cases.
HTML Constraints
Use built-in HTML constraints because they make the first pass faster for the user:
<form method="post" action="/register">
<label>
Email
<input name="email" type="email" required autocomplete="email">
</label>
<label>
Display name
<input name="display_name" required minlength="2" maxlength="60">
</label>
<label>
Password
<input name="password" type="password" required minlength="12" autocomplete="new-password">
</label>
<button type="submit">Create account</button>
</form> MDN is explicit that client-side validation is a usability feature. The server still has to repeat the rules because the browser is not the authority. Anyone can send a POST request without your form.
Use the HTML layer for fast feedback. Use PHP for the decision that matters.
Validate into a Known Shape
Start by pulling raw input into local values, trimming where trimming is part of the rule, and returning errors alongside the cleaned data.
<?php
/**
* @return array{0: array<string, string>, 1: array<string, string>}
*/
function validateRegistration(array $input): array
{
$email = trim((string) ($input["email"] ?? ""));
$displayName = trim((string) ($input["display_name"] ?? ""));
$password = (string) ($input["password"] ?? "");
$errors = [];
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
$errors["email"] = "Enter a valid email address.";
}
$displayNameLength = mb_strlen($displayName, "UTF-8");
if ($displayNameLength < 2 || $displayNameLength > 60) {
$errors["display_name"] = "Use 2 to 60 characters.";
}
if (strlen($password) < 12) {
$errors["password"] = "Use at least 12 characters.";
}
return [
$errors,
[
"email" => $email,
"display_name" => $displayName,
"password" => $password,
],
];
} PHP's FILTER_VALIDATE_EMAIL is a syntax check, not a deliverability guarantee. It tells you whether the string looks like an email address. It does not prove the mailbox exists, that the user owns it, or that mail will arrive. Confirmation emails and bounce handling are different layers.
For text length, mb_strlen() is usually the right shape because a display name is made of characters, not bytes. For password minimum length, byte length is acceptable when the rule is only a lower bound and the application does not truncate passwords. Never silently cut a password to fit a column. Store a password hash, not the password.
Error Display
A form handler should verify the request, validate input, then either redisplay the form with errors or write the accepted data.
<?php
session_start();
if ($_SERVER["REQUEST_METHOD"] === "POST") {
verifyCsrfToken($_POST["csrf_token"] ?? "");
[$errors, $data] = validateRegistration($_POST);
if ($errors !== []) {
require __DIR__ . "/../views/register.php";
return;
}
createUser(db(), $data["email"], $data["display_name"], $data["password"]);
header("Location: /welcome", true, 303);
return;
}
require __DIR__ . "/../views/register.php"; The 303 redirect after a successful POST is a small discipline that still matters. It prevents a refresh from resubmitting the form and keeps the browser history honest.
CSRF protection belongs in this flow because form validation and request legitimacy are different questions. Read PHP security fundamentals before accepting real account data, especially if the form changes state.
Database Constraints Are the Final Guard
Validation gives the user useful errors before the write. The database still needs to protect the facts.
CREATE TABLE users (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
display_name TEXT NOT NULL CHECK (char_length(display_name) BETWEEN 2 AND 60),
password_hash TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
); The unique email rule belongs in the database because two PHP requests can race each other. One request validates, another request inserts, and then the first request tries to insert the same email. The database is the layer that can settle that race reliably.
This is where form validation crosses into schema design. If a rule must remain true no matter which PHP file, queue worker, import script, or admin tool writes the row, the schema should carry it.
Escape Output at Render Time
Accepted input is still input, so escape it when rendering HTML:
<input
name="display_name"
value="<?= htmlspecialchars($data["display_name"] ?? "", ENT_QUOTES | ENT_SUBSTITUTE, "UTF-8") ?>"> That is output escaping rather than validation, because the user may be allowed to have an apostrophe or angle bracket in a field depending on the application. The question is how that value is represented safely in HTML.
If a field intentionally accepts HTML, do not treat htmlspecialchars() as a full content policy. Use a real sanitizer with a strict allowlist, and be honest about the risk.
Spellchecking
PHP's pspell extension is documented as deprecated and unbundled as of PHP 8.4.0. Any script that still calls those functions is running on an extension the language no longer ships, so the spelling half of this topic needs a different answer.
For ordinary text fields, use the browser's spelling support:
<textarea
name="body"
rows="8"
spellcheck="true"
maxlength="5000"></textarea> The browser can underline likely spelling mistakes without sending the user's draft to your server or blocking the submission. That is the right default for comments, notes, profile text, and admin copy.
If your product really needs server-side spellchecking, treat it as an editorial workflow. Run it after the text is saved as a draft, show suggestions as suggestions, and let a human decide. Do not reject a name, product code, domain, username, or medical term because a dictionary disagrees with it.
That dictionary problem is not theoretical; older validation scripts loved regular expressions and word lists because they gave the appearance of certainty. Real user data is messier than that, and a good validator enforces the rules the application actually needs, not the rules that are easiest to write.
Keep the Database Boundary Clean
Once validation passes, the insert still uses prepared statements:
<?php
function createUser(PDO $pdo, string $email, string $displayName, string $password): void
{
$statement = $pdo->prepare(
"INSERT INTO users (email, display_name, password_hash)
VALUES (:email, :display_name, :password_hash)",
);
$statement->execute([
"email" => $email,
"display_name" => $displayName,
"password_hash" => password_hash($password, PASSWORD_DEFAULT),
]);
} Prepared statements handle the SQL value boundary, but they do not replace validation, and validation does not replace prepared statements, so both layers stay in place.
For a fuller database-backed shape, read PHP and MySQL with PDO and building a small PHP database app. If the same input arrives as JSON instead of a browser form, the validation principle is unchanged; the PHP API guide covers that boundary. When the accepted form data triggers a notification, continue to sending email in PHP.
The Practical Rule
Form data has to be checked before it becomes application data, and the answer is less about clever regular expressions than about clear ownership.
Validate for shape, protect the request, store through prepared statements, enforce durable facts in SQL, and escape when rendering. Let spellcheck help the writer, not police the database.
That is the shape that still holds after the framework changes.
For the broader sequence, return to the PHP tutorials track.
This example has been updated from the original CodeWalkers form and spelling validation tutorial, which used hand-written regular expressions and PHP's pspell functions. Both have been overtaken: filter_var() accepts valid addresses those patterns rejected, and pspell is deprecated and unbundled as of PHP 8.4.0.
Frequently Asked Questions
How should you validate an email address in PHP?
Use filter_var with FILTER_VALIDATE_EMAIL for the syntax check. It is better maintained than any hand-rolled pattern and accepts unusual but valid addresses, such as plus-tags and newer top-level domains, that homemade regular expressions reject. Syntax is not deliverability: only a clicked confirmation link proves the mailbox exists.
Should you trim whitespace before validating?
Yes, for text fields where leading or trailing spaces carry no meaning, such as names and email addresses. Trim first so a stray space from a copy-paste does not fail a length rule. Do not trim passwords, where every character counts.
How do you validate a date submitted by a form?
Parse it against the exact format you expect, then confirm the parsed date matches what was submitted. That catches values such as 31 February, which look valid as text but roll over into the next month when converted.
Should validation messages name the field that failed?
Yes, and they should say what is required rather than only that something is wrong. Attach the message to the field, keep the entered values in the form, and move focus to the first error so the person can fix it without hunting.
Does every text input need spellchecking?
No, and browsers already spellcheck most text fields by default, so the real decision is where to switch it off with spellcheck="false": names, codes, usernames, and reference numbers, where a red underline tells someone their correct value looks like a typo.
Sources
-
[1]
PHP filter validation examples(php.net)
-
[2]
PHP htmlspecialchars(php.net)
-
[3]
PHP mb_strlen(php.net)
-
[4]
PHP Pspell(php.net)
-
[5]
Client-side form validation(developer.mozilla.org)
-
[6]
spellcheck HTML global attribute(developer.mozilla.org)
-
[7]
Input Validation Cheat Sheet(cheatsheetseries.owasp.org)
-
[8]
Cross-Site Request Forgery Prevention Cheat Sheet(cheatsheetseries.owasp.org)
-
[9]
PostgreSQL Constraints(postgresql.org)
Read Next
Validate email-address shape in PHP with filter_var, then separate syntax checks from confirmation, deliverability, and account ownership.
Secure ordinary PHP surfaces with current password hashing, sessions, CSRF tokens, output escaping, prepared statements, and upload controls.
Design relational tables through a worked normalization example, then add stable keys, constraints, indexes, and safe migrations.