Email validation is one of those tasks that seems straightforward until you start thinking about edge cases. What about plus signs in addresses? Subdomains? International top-level domains? Two-letter country codes?
The honest answer is that truly complete email validation is remarkably complex — the full RFC 5322 specification runs to dozens of pages. But for the vast majority of real-world use cases, a well-written regular expression combined with a basic format check is all you need.
This guide covers everything from a simple one-liner to a production-ready validation function with clear explanations of what each part does and why.
The Simplest Approach: A Basic Regex
For most contact forms, signup pages, and newsletter inputs, this regex catches the overwhelming majority of invalid emails:
function isValidEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
console.log(isValidEmail("user@example.com")); // true
console.log(isValidEmail("user.name@domain.co")); // true
console.log(isValidEmail("notanemail")); // false
console.log(isValidEmail("missing@domain")); // false
console.log(isValidEmail("@nodomain.com")); // false
console.log(isValidEmail("spaces in@email.com")); // false
Breaking down the regex /^[^\s@]+@[^\s@]+\.[^\s@]+$/:
^— Start of string[^\s@]+— One or more characters that are not whitespace or@@— The literal @ symbol[^\s@]+— One or more characters that are not whitespace or@(domain name)\.— A literal dot[^\s@]+$— One or more characters that are not whitespace or@(TLD)
This is intentionally permissive. It verifies the basic structure — something before @, a domain, a dot, and a TLD — without rejecting valid but unusual formats.
A More Thorough Validation Regex
For tighter validation that catches more edge cases:
function isValidEmail(email) {
const regex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}$/;
return regex.test(email.trim().toLowerCase());
}
console.log(isValidEmail("user@example.com")); // true
console.log(isValidEmail("user+tag@example.co.uk")); // true
console.log(isValidEmail("user.name@subdomain.org")); // true
console.log(isValidEmail("user@-invalid.com")); // false
console.log(isValidEmail("user@domain.c")); // false (TLD too short)
console.log(isValidEmail("user@domain.123")); // false (numeric TLD)
This version:
- Allows the full set of valid characters in the local part (
!#$%&'*+/=?^_) - Validates domain name structure (no leading or trailing hyphens)
- Requires a TLD of at least two characters
- Rejects numeric-only TLDs
- Handles subdomains like
mail.example.co.uk
Note the .trim().toLowerCase() — trimming whitespace before testing prevents false negatives from copy-paste with trailing spaces, and lowercasing normalizes the input since email addresses are case-insensitive in the domain part.
What Regex Cannot Do
Regex can check the format of an email address. It cannot tell you whether the address actually exists.
totally-made-up@realexample.com would pass any regex validator. The domain is real. The format is correct. But the mailbox does not exist.
The only way to truly verify that an email address is deliverable is to send a message to it and confirm receipt. Every other check is format validation, not existence validation.
For most applications, format validation is enough — you just want to prevent obvious typos and blank submissions. For critical flows where deliverability matters, combine format validation with a confirmation email.
A Production-Ready Validation Function
Here is a complete validation function that handles format checking, whitespace trimming, length limits, and useful error messages:
function validateEmail(email) {
// Trim whitespace
const trimmed = (email || "").trim();
// Check for empty input
if (!trimmed) {
return { valid: false, error: "Email address is required." };
}
// Check length (RFC 5321 limits total address to 254 characters)
if (trimmed.length > 254) {
return { valid: false, error: "Email address is too long." };
}
// Check local part length (before @) — max 64 characters
const atIndex = trimmed.indexOf("@");
if (atIndex > 64) {
return { valid: false, error: "The part before @ is too long." };
}
// Check format with regex
const regex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}$/;
if (!regex.test(trimmed)) {
return { valid: false, error: "Please enter a valid email address." };
}
return { valid: true, value: trimmed.toLowerCase() };
}
// Usage
const result = validateEmail(" User@Example.COM ");
if (result.valid) {
console.log("Valid:", result.value); // "user@example.com"
} else {
console.log("Error:", result.error);
}
const bad = validateEmail("notvalid");
console.log(bad); // { valid: false, error: "Please enter a valid email address." }
This function returns an object with valid, an error message when invalid, and the cleaned value when valid. The caller gets everything needed to either proceed or display a helpful error to the user.
Validating in a Form
Here is how to wire up email validation to a form submission:
<form id="signup-form">
<label for="email">Email Address</label>
<input type="email" id="email" name="email" placeholder="you@example.com" />
<span id="email-error" class="error-message"></span>
<button type="submit">Sign Up</button>
</form>
const form = document.getElementById("signup-form");
const emailInput = document.getElementById("email");
const emailError = document.getElementById("email-error");
function showError(message) {
emailError.textContent = message;
emailInput.classList.add("invalid");
}
function clearError() {
emailError.textContent = "";
emailInput.classList.remove("invalid");
}
// Validate on blur (when user leaves the field)
emailInput.addEventListener("blur", function () {
const result = validateEmail(this.value);
if (!result.valid) {
showError(result.error);
} else {
clearError();
}
});
// Validate on form submit
form.addEventListener("submit", function (event) {
event.preventDefault();
const result = validateEmail(emailInput.value);
if (!result.valid) {
showError(result.error);
emailInput.focus();
return;
}
clearError();
console.log("Form submitted with email:", result.value);
// Proceed with form submission
});
Validating on blur gives users immediate feedback when they move to the next field. Validating again on submit catches cases where the user skips directly to the button.
Using the HTML5 Email Input Type
The type="email" attribute on an <input> element gives you basic browser-native validation for free:
<input type="email" name="email" required />
When a form with type="email" is submitted, the browser validates the format and prevents submission if the value does not look like an email. The error message is styled by the browser and appears automatically.
Browser validation is a useful first line of defense. Its limitations:
- Validation criteria vary slightly between browsers
- It cannot be customized — you get the browser’s error message, not your own
- It only fires on form submission, not as the user types
Use type="email" always as a baseline. Add JavaScript validation on top for custom messages, real-time feedback, and consistency across browsers.
Validating Multiple Emails
When a field accepts a comma-separated list of email addresses:
function validateEmailList(input) {
const emails = input.split(",").map(e => e.trim()).filter(Boolean);
if (emails.length === 0) {
return { valid: false, error: "Please enter at least one email address." };
}
const invalid = emails.filter(email => !validateEmail(email).valid);
if (invalid.length > 0) {
return {
valid: false,
error: `Invalid email${invalid.length > 1 ? "s" : ""}: ${invalid.join(", ")}`
};
}
return { valid: true, values: emails.map(e => e.toLowerCase()) };
}
const result = validateEmailList("alice@example.com, bob@example.com, notvalid");
console.log(result);
// { valid: false, error: "Invalid email: notvalid" }
const good = validateEmailList("alice@example.com, bob@example.com");
console.log(good);
// { valid: true, values: ["alice@example.com", "bob@example.com"] }
Split on commas, trim each entry, validate individually, and report all invalid ones at once rather than making the user fix them one at a time.
Checking for Disposable Email Domains
Some applications block temporary email services like Mailinator, Guerrilla Mail, or 10-minute email addresses. The simplest approach is a blocklist check:
const DISPOSABLE_DOMAINS = [
"mailinator.com",
"guerrillamail.com",
"tempmail.com",
"10minutemail.com",
"throwaway.email",
"yopmail.com"
];
function isDisposableEmail(email) {
const domain = email.split("@")[1]?.toLowerCase();
return DISPOSABLE_DOMAINS.includes(domain);
}
console.log(isDisposableEmail("user@mailinator.com")); // true
console.log(isDisposableEmail("user@gmail.com")); // false
Disposable email services run into the thousands. A static blocklist only catches the most common ones. For comprehensive coverage, dedicated APIs like AbstractAPI, Hunter.io, or Kickbox provide real-time disposable domain detection.
Tips for Email Validation in JavaScript
Always validate on the server too. Client-side validation is for user experience — it prevents obvious mistakes before form submission. It is not a security measure. JavaScript can be bypassed entirely. Validate again server-side before processing or storing any email address.
Do not over-validate. Rejecting addresses like user+tag@example.com or user@sub.domain.co.uk frustrates real users with legitimate addresses. The permissive regex catches the format without rejecting valid edge cases.
Trim and lowercase before storing. Leading and trailing whitespace causes failed lookups. Email domains are case-insensitive, so storing in lowercase prevents duplicate accounts from different capitalizations.
Normalize the + alias. Many email providers allow user+anything@gmail.com to deliver to user@gmail.com. If you want to detect duplicate accounts that use + aliases, strip everything between + and @ before comparing:
function normalizeEmail(email) {
const [local, domain] = email.toLowerCase().split("@");
const normalized = local.split("+")[0];
return `${normalized}@${domain}`;
}
console.log(normalizeEmail("user+tag@gmail.com")); // "user@gmail.com"
Send a confirmation email for anything critical. Format validation confirms the structure. A confirmation email confirms the address is real and accessible by the person who entered it. For account registration, password reset, and purchase confirmation, the confirmation step is not optional…
Email validation in JavaScript means verifying format, not existence. A solid regex handles the format check. The validateEmail() function above wraps that regex in length checks and useful error messages for real form usage.
Combine it with type="email" on your inputs for browser-native baseline validation, validate on both blur and submit for the best user experience, and always repeat the validation server-side before trusting any input.
The email address you store in your database is only as clean as the validation that produced it.