Blog / Coding tips

Regex for Email Validation in JavaScript (Tested)

Cover image for Regex for Email Validation in JavaScript (Tested)

A simple, reliable regex for email validation in JavaScript is /^[^\s@]+@[^\s@]+\.[^\s@]+$/. It checks that there's some text, one @, a domain, a dot and an ending, with no spaces, and you use it with EMAIL_RE.test(email.trim()). It deliberately doesn't try to be perfect, because no regex can tell you whether an address really exists; only sending an email can do that. Below I compare it with two other popular patterns on ten real-looking addresses, show a "strict" regex that can freeze the page, and wire it into a form.

I've used this check on the contact form of my portfolio for a while. Before that I copied a long "RFC compliant" pattern from a forum, and it rejected a friend's perfectly valid Irish address. Every example below was run with Node 20 or in headless Chrome, and the outputs are copied exactly.

The email regex I recommend

// email.js: a practical email check for forms
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

function isValidEmail(value) {
  const email = String(value).trim();
  return email.length <= 254 && EMAIL_RE.test(email);
}

module.exports = { EMAIL_RE, isValidEmail };

Reading the pattern from left to right:

  • ^ and $ anchor it to the start and end, so the whole string has to match, not just part of it.
  • [^\s@]+ means "one or more characters that are not whitespace and not @". That's the part before the @.
  • @ is a literal at sign, exactly one.
  • [^\s@]+\.[^\s@]+ is the domain: some text, a literal dot (escaped as \.), and more text.

The function also trims spaces, because people often paste an address with a space at the end, and limits the length to 254 characters, the practical maximum for an address.

Comparing three email regex patterns

Here are three patterns you'll come across: the simple one above, a popular "strict" one that only allows certain characters, and the pattern the HTML specification uses for <input type="email">, which is what browsers check against:

const { isValidEmail } = require("./email");

// The pattern browsers use for <input type="email"> (from the HTML spec)
const HTML_SPEC_RE = /^[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 popular "strict" pattern you'll find in lots of tutorials
const STRICT_RE = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

const samples = [
  "aisha@example.co.uk",
  "tom.riley+news@gmail.com",
  "  zoe@example.com  ",
  "o'connor@example.ie",
  "rahim@localhost",
  "no-at-sign.example.com",
  "two@@example.com",
  "spaces in@example.com",
  "dot@example.",
  "zoë@exämple.com",
];

const yes = (b) => (b ? "yes" : "no");
console.log("email                        simple  strict  html-spec");
for (const s of samples) {
  console.log(
    JSON.stringify(s).padEnd(29),
    yes(isValidEmail(s)).padEnd(7),
    yes(STRICT_RE.test(s.trim())).padEnd(7),
    yes(HTML_SPEC_RE.test(s.trim())),
  );
}

// Output:
// email                        simple  strict  html-spec
// "aisha@example.co.uk"         yes     yes     yes
// "tom.riley+news@gmail.com"    yes     yes     yes
// "  zoe@example.com  "         yes     yes     yes
// "o'connor@example.ie"         yes     no      yes
// "rahim@localhost"             no      no      yes
// "no-at-sign.example.com"      no      no      no
// "two@@example.com"            no      no      no
// "spaces in@example.com"       no      no      no
// "dot@example."                no      no      no
// "zoë@exämple.com"             yes     no      no

Things I learnt from this table:

  • The strict pattern rejects real addresses. o'connor@example.ie is valid; apostrophes are allowed in email addresses, and people with names like O'Connor or O'Brien get blocked by sites all the time.
  • It also rejects international characters like zoë@exämple.com. Internationalised addresses are rare, but they're real, and the simple pattern lets them through.
  • The browser's own check allows rahim@localhost, because a domain without a dot is technically valid on a private network. On a public sign-up form you almost always want a dot, which is why I still add a JavaScript check on top of type="email".
  • All three agree on the obvious mistakes: no @, two @s, a space, and a missing ending.

Why the "perfect" email regex is a bad idea

The full rules for email addresses (RFC 5322) allow quoted strings, comments and IP addresses in square brackets. Regexes that try to cover all of that are thousands of characters long, impossible to read, and still wrong in some cases. Worse, badly written patterns can be slow. Here's a short-looking pattern from an old forum answer, timed against my simple one on inputs that almost match:

const { isValidEmail } = require("./email");

// A pattern copied from an old forum answer: a + inside a group that also has +
const RISKY_RE = /^([a-zA-Z0-9]+\.?)+@example\.com$/;

for (const n of [20, 24, 28]) {
  const input = "a".repeat(n) + "@example.co";   // almost matches, then fails
  let t = performance.now();
  RISKY_RE.test(input);
  const risky = performance.now() - t;
  t = performance.now();
  isValidEmail(input);
  const simple = performance.now() - t;
  console.log(`${n + 11} chars: risky regex ${Math.round(risky)} ms, simple regex ${simple.toFixed(2)} ms`);
}

// Output:
// 31 chars: risky regex 39 ms, simple regex 0.20 ms
// 35 chars: risky regex 94 ms, simple regex 0.18 ms
// 39 chars: risky regex 1464 ms, simple regex 0.02 ms

Every four extra characters made the risky regex several times slower, and at 39 characters it blocked the JavaScript thread for well over a second. That's called catastrophic backtracking: the nested + inside a group that also has + gives the engine an exponential number of ways to try splitting the text. In a browser the page freezes; on a Node server, one request can stall everyone else's. Your timings will differ, but the pattern of growth won't. The simple regex has no nested quantifiers, so it stays fast whatever you throw at it.

Using the regex in a form

In a real form I combine three things: type="email" so mobile keyboards show the @ key, the regex check on submit, and a clear message next to the field:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Email check</title>
</head>
<body>
  <form id="signup" novalidate>
    <label for="email">Email</label>
    <input id="email" name="email" type="email" required autocomplete="email">
    <p id="email-error" aria-live="polite"></p>
    <button type="submit">Sign up</button>
  </form>

  <script>
    const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    const form = document.getElementById("signup");
    const input = document.getElementById("email");
    const error = document.getElementById("email-error");

    form.addEventListener("submit", (event) => {
      const email = input.value.trim();
      let message = "";
      if (email === "") message = "Please enter your email address.";
      else if (!EMAIL_RE.test(email)) message = "That doesn't look like an email address. Check for a missing @ or dot.";

      error.textContent = message;
      input.setAttribute("aria-invalid", String(message !== ""));
      if (message) {
        event.preventDefault();
        input.focus();
      }
    });
  </script>
</body>
</html>

I tested it in headless Chrome by submitting an empty field, an address without a dot, and a valid address:

"" -> submitted=false, error="Please enter your email address."
"aisha@example" -> submitted=false, error="That doesn't look like an email address. Check for a missing @ or dot."
"aisha@example.co.uk" -> submitted=true, error=""

The novalidate attribute turns off the browser's built-in pop-up so my own message is shown instead, which looks the same in every browser. aria-live="polite" makes screen readers read out the error, and aria-invalid marks the field. My post on HTML form input types and validation covers type="email", required and the other built-in checks in more detail, and MDN's page on <input type="email"> lists every attribute it supports.

Always validate on the server too

JavaScript validation is there to help honest users fix typos quickly. It doesn't protect anything, because anyone can turn JavaScript off or send a request straight to your server. Check the email again on the server before you store it or send to it. In PHP that's filter_var($email, FILTER_VALIDATE_EMAIL), which I show in my PHP form validation and sanitisation guide. If you're sending the form with JavaScript, my fetch POST JSON example shows how to display the server's error message back to the user.

Making sure the address actually works

A regex can only check the shape of an address. aisha@example.co.uk passes every pattern above, but nobody reads that inbox. If it matters that the address is real, for example for password resets, send a confirmation link and only activate the account once it's clicked. That's the only check that is 100% accurate.

Common mistakes

  • Forgetting ^ and $. Without them, "hello a@b.co world" passes because part of it matches.
  • Forgetting to escape the dot. An unescaped . means "any character", so a@bxco would pass.
  • Using the g flag with test(). A global regex remembers lastIndex between calls, so the same valid address can fail every second time.
  • Lower-casing the whole address. The domain isn't case-sensitive, but technically the part before the @ can be. Most providers ignore case, so lower-casing for comparison is fine; just store what the user typed.

FAQ

What is the best regex for email validation in JavaScript?

For most forms, /^[^\s@]+@[^\s@]+\.[^\s@]+$/. It catches real typos, accepts unusual but valid addresses, and can't cause catastrophic backtracking. Confirm the address by email if it really matters.

How do I validate an email address in JavaScript without regex?

Use an <input type="email"> and call input.checkValidity(), which uses the browser's built-in pattern. Bear in mind that it accepts addresses without a dot in the domain, such as name@localhost.

Is HTML5 type="email" validation enough?

It's a good first layer and gives mobile users the right keyboard, but it allows dotless domains and can be bypassed. Add your own check if you need a dot, and always validate again on the server.

Can regex check if an email address exists?

No. A regex only checks the format. To know an address exists and belongs to the user, send it a confirmation link or code.

// note

How to read this note.

This is a learning note from studying the web. It is one small topic, written so I can remember it. It is not a course and not a claim that I have finished the subject.

If a sentence is wrong, say so from the contact page and name this title. Drafts never appear here. Related notes, when they exist, are other published posts, and the same sample rule applies to each of them.

Related notes

How to Work Out Your UK Degree Classification
Coding tips

How to Work Out Your UK Degree Classification

Work out a First, 2:1 or 2:2 from module marks: credit-weighted averages, 30:70 or 1:2 year weightings, rounding at 69.5 and the mark you need.

October 4, 2026 · 10 min read