SendForm.net

Types of Form Validation and When Each One Applies

Illustration of layered HTML form validation, with email, message, security, and status checks verifying submitted form data.

HTML form validation comes in a few distinct flavours, and each one has a job the others can't do. Browser-native validation (using attributes like required, type="email", and pattern) catches obvious mistakes instantly, JavaScript validation handles rules the browser doesn't understand, and server-side validation is the only layer you can actually trust because it's the only one a user can't bypass. Most solid forms use all three, layered, with inline feedback on top to tell people what went wrong while they're still looking at the field.

The four types of form validation at a glance

People usually say there are two types of form validation, client-side and server-side. That's true at the architecture level, but in practice you're making four separate decisions: where the check runs, who wrote it, when it fires, and how the message appears.

Type Runs where Good at Can be bypassed?
Native HTML validation Browser, before submit Empty fields, basic formats, min/max, length limits Yes (DevTools, curl, JS off)
JavaScript / custom client-side validation Browser, on input or submit Cross-field rules, custom messages, async checks Yes
Server-side validation Your backend or form service Security, data integrity, business rules, uniqueness No
Inline validation (feedback layer) Browser UI Telling the user what's wrong, field by field N/A (it's presentation)

Client-side validation with built-in HTML attributes

This is the cheapest validation you will ever write, because you barely write it. Add attributes to your inputs and the browser enforces them before the form submits, in the user's own language, with zero JavaScript.

<form action="/subscribe" method="POST">
  <label for="email">Email</label>
  <input id="email" type="email" name="email" required maxlength="255">

  <label for="age">Age</label>
  <input id="age" type="number" name="age" min="18" max="120" step="1">

  <label for="zip">ZIP code</label>
  <input id="zip" name="zip" pattern="[0-9]{5}" title="Five digits, e.g. 90210">

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

The attributes that do the heavy lifting:

  • required blocks empty submission. It works differently on checkboxes, radio groups, and hidden fields, which is where most bugs come from (there's a longer breakdown of when the required attribute actually works).
  • type gives you free format checks: email, url, number, date, tel (note that tel enforces nothing, it only changes the mobile keyboard).
  • min, max, step for numbers and dates.
  • minlength and maxlength for text length.
  • pattern for anything regex-shaped, like postal codes or invoice numbers. See the guide to using the pattern attribute for input validation for the gotchas around anchoring and Unicode.
Native validation only fires on real submits. If you submit with fetch() or form.submit(), the browser skips constraint checking entirely. Call form.reportValidity() yourself first.

Two structural limits worth knowing: <textarea> ignores pattern completely, so length limits and JS are your only tools there (more on that in these HTML textarea best practices ). And a free-text input paired with a <datalist> accepts values that aren't in the list, which is exactly the difference explained in datalist vs select. A <select> with required and an empty first option is the only version that guarantees a value from your list.

JavaScript validation for rules HTML can't express

HTML attributes can't compare two fields, look something up, or say "this password needs a symbol, and here's a friendly reason why". That's the job of the Constraint Validation API , which lets you read the browser's own verdict and add your own on top.

const pw = document.querySelector('#password');
const confirm = document.querySelector('#confirm');

function checkMatch() {
  confirm.setCustomValidity(
    confirm.value === pw.value ? '' : 'Passwords do not match.'
  );
}

confirm.addEventListener('input', checkMatch);
pw.addEventListener('input', checkMatch);

Reach for client-side JavaScript validation when you need:

  • Cross-field rules: end date after start date, password confirmation, "at least one contact method".
  • Custom wording: "Use your work email" reads better than the browser's generic message.
  • Async checks: is this username taken, is this coupon still valid.
  • Conditional requirements: a field that only becomes required after a checkbox is ticked.
  • Progressive step gating in a multi-step form , where you validate each step before advancing.

Use ValidityState properties (valueMissing, typeMismatch, patternMismatch, tooShort, rangeOverflow) rather than rewriting format checks from scratch. The browser already knows what's wrong; you just need to phrase it better.

Server-side validation and why it is never optional

Everything above runs on a machine you don't control. A browser extension, a proxy, DevTools, or a two-line curl command can submit any payload it likes straight to your endpoint. So client-side validation is a user experience feature. Server-side validation is the actual rule.

Your server needs to re-check everything the client checked, plus the things the client couldn't:

  • Re-verify format and length. Never trust that email is an email or that a 3000-character limit was respected.
  • Type and range coercion. A number field can arrive as "abc", "1e9", or an array.
  • Uniqueness and existence. Duplicate emails, valid product IDs, real order numbers.
  • Authorisation. Is this user even allowed to edit this record? No client check can answer that.
  • Sanitising and escaping before storage or display, following the OWASP input validation guidance.
  • File checks. Actual MIME type and real byte size, not the filename extension. And the request has to be encoded correctly in the first place, which is where the form enctype attribute matters for uploads.
  • Abuse controls. Rate limits, spam scoring, honeypot handling, CSRF tokens.
A useful rule: if breaking the rule would corrupt your data, cost you money, or expose someone's information, it must be enforced server-side. Client-side checks on the same rule are a courtesy, not a control.

Inline validation vs on-submit validation

Same rules, different moment. Timing changes how the form feels more than almost anything else.

Approach Fires Best for Risk
Inline on blur When the user leaves a field Most fields: email, phone, postal code Feels harsh if the user only tabbed through
Inline live (on input) Every keystroke Password strength, character counters, username availability Yelling "invalid email" after one letter is typed
On submit When the user clicks send Cross-field rules, short forms, final safety net User fixes errors one round-trip at a time
After server response After the request completes Duplicate records, expired codes, payment failures Losing entered data if you don't repopulate the form

The pattern that annoys the fewest people: validate on blur for the first pass, then switch that field to live validation once it has an error, so the message clears the moment they fix it. Never validate a field before the user has touched it.

When each validation type applies

Concrete mapping, field by field:

  • Simple contact form (name, email, message): native HTML attributes plus server-side re-validation. JavaScript is optional here.
  • Signup with password confirmation: native for format, JavaScript for the match check and strength meter, server-side for uniqueness and hashing rules.
  • Checkout address: native pattern for postal codes, JavaScript to swap the pattern when the country changes, server-side address verification.
  • File upload: accept and a client size check for fast feedback, server-side type and size enforcement as the real gate.
  • Date range booking: native min / max, JavaScript for "checkout after checkin", server-side for real availability.
  • Anything touching money, accounts, or other people's data: full stack of all three, no exceptions.

One more layer that isn't really validation but sits next to it: bot filtering. Format rules won't stop automated submissions, which is why honeypot fields and server-side rate limiting exist alongside your field checks.

Writing validation feedback people can act on

An error message has one job: tell the user exactly what to change. "Invalid input" fails that test. Specific beats polite.

  • Name the field and the fix: "Phone number needs 10 digits, no spaces" instead of "Please enter a valid phone number".
  • Put the message next to the field, not only in a banner at the top.
  • Use aria-describedby to link the input to its error text, and aria-invalid="true" when it fails, so screen readers announce it. This is what WCAG 3.3.1 Error Identification asks for.
  • Don't rely on red alone. Colour plus icon plus text.
  • Move focus to the first invalid field after a failed submit, and keep everything the user already typed.
  • Show a success state sparingly. Green ticks on every field become noise; they help most on fields with strict rules like passwords or usernames.

Browser default bubbles are fine for a prototype, but they disappear on scroll and can't be styled. If you want consistent, accessible messaging, suppress them with novalidate on the form, keep the attributes for their validity state, and render your own messages from the constraint validation API.

Serverless contact form with HTML form validation and server-side checks

Get server-side validation without running a server

Native HTML form validation is easy, but the server-side half needs a backend you may not have. SendForm's serverless contact form validates and stores each submission, filters spam, and emails it to you, so a static site still gets the layer users can't bypass.

Create a contact form →

No. Anyone can delete the required attribute in DevTools or post directly to your endpoint with curl, skipping every browser check. Client-side validation improves speed and clarity, but the receiving side still has to validate format, length, and content before storing or emailing anything.

Three usual causes: the form has the novalidate attribute, the submit button carries formnovalidate, or you're submitting through JavaScript with fetch() or form.submit(), which bypasses constraint checking. In the JavaScript case, call form.reportValidity() before sending the request.

Validate on blur for the first check, since flagging an email as invalid after one typed character is frustrating. Once a field is marked invalid, switch it to live validation on input so the error disappears the instant the user corrects it. Keep live-only validation for password strength and character counters.

No. It only checks the shape of the string, roughly "something@something". a@b passes. Real verification needs a server-side check, usually a domain lookup or a confirmation link sent to the address. Treat type="email" as typo prevention, not proof of a working mailbox.

Set aria-invalid="true" on the failing input and point aria-describedby at the element holding the error text. Keep the message as real text, not just a colour or icon, move focus to the first invalid field after a failed submit, and make sure every input has a properly associated label.

Never on their own. A pattern attribute runs in the browser and can be stripped out in seconds. Mirror the same rule on the server, and keep patterns simple: overly clever regex for emails or phone numbers rejects valid real-world values more often than it blocks bad ones.