Form validation is one of those tasks every developer has to implement, usually multiple times across different projects. And every time, the same question comes up: what's the right regex for validating this field?
This guide answers that question for the three most common validation scenarios — email addresses, URLs, and phone numbers — with tested patterns, real code in JavaScript, Python, and PHP, and honest notes on where regex alone isn't enough.
Contents
user@example.com looks like a valid email — it cannot confirm the mailbox exists. Keep this distinction in mind throughout.
1. Email Address Validation
Email validation is deceptively complex. The full RFC 5322 specification for valid email addresses is extraordinarily permissive — technically, "user name"@[192.168.1.1] is a valid email. In practice, you want a pattern that catches typos without being so strict it rejects real addresses.
The Patterns — From Simple to Strict
| Level | Pattern | Allows | Use when |
|---|---|---|---|
| Basic | [\w.+-]+@[\w-]+\.[\w.]+ | Most emails | Quick extraction from text |
| Standard ✅ | ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ | 99% of real emails | Form validation (recommended) |
| Strict | ^(?!.*\.\.)(?!.*\.$)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z]{2,}$ | RFC closer | When false positives are costly |
user+tag@sub.example.co.uk.
Email Validation in JavaScript
// Standard email validation
function isValidEmail(email) {
const pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return pattern.test(email.trim());
}
// Test cases
isValidEmail("user@example.com"); // ✅ true
isValidEmail("user+tag@sub.domain.co.uk"); // ✅ true
isValidEmail("user.name@company.io"); // ✅ true
isValidEmail("notanemail"); // ❌ false
isValidEmail("missing@domain"); // ❌ false
isValidEmail("@nodomain.com"); // ❌ false
// For React forms
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
const isValid = emailRegex.test(formData.email);
Email Validation in Python
import re
def is_valid_email(email: str) -> bool:
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email.strip()))
# Extracting multiple emails from text
def extract_emails(text: str) -> list:
pattern = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
return re.findall(pattern, text)
# Test
is_valid_email("alice@example.com") # → True
is_valid_email("not-valid") # → False
Email Validation in PHP
// Method 1: PHP built-in filter (recommended)
function isValidEmail($email): bool {
return filter_var(trim($email), FILTER_VALIDATE_EMAIL) !== false;
}
// Method 2: Regex (when you need custom rules)
function isValidEmailRegex($email): bool {
$pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
return (bool) preg_match($pattern, trim($email));
}
// PHP tip: always use filter_var for email — it handles edge cases better than regex
filter_var($email, FILTER_VALIDATE_EMAIL) is more accurate than a hand-rolled regex pattern. Use it when PHP is available; use regex in other languages or when you need custom rules.
2. URL Validation
URL validation is where regex quickly becomes unreliable — URLs can be extraordinarily complex, and edge cases multiply fast. The right approach depends on what you actually need to validate.
Choosing Your Approach
| Approach | When to use | Pros | Cons |
|---|---|---|---|
| URL constructor (JS) | JavaScript validation | Most accurate, handles all edge cases | JS only |
| Regex (simple) | Basic http/https check | Fast, cross-language, customizable | Edge cases |
| filter_var (PHP) | PHP URL validation | Built-in, reliable | PHP only |
| urllib.parse (Python) | Python URL parsing | Full URL decomposition | Python only |
URL Validation in JavaScript — Best Practice
// Method 1: URL constructor (recommended — most accurate)
function isValidUrl(string) {
try {
const url = new URL(string);
return url.protocol === 'http:' || url.protocol === 'https:';
} catch {
return false;
}
}
// Method 2: Regex (when URL constructor isn't available)
function isValidUrlRegex(url) {
const pattern = /^https?:\/\/(www\.)?[-\w]+\.\w{2,}(\/\S*)?$/i;
return pattern.test(url);
}
// Test cases
isValidUrl("https://youkip.com"); // ✅
isValidUrl("https://sub.domain.co.uk/path"); // ✅
isValidUrl("http://192.168.1.1:8080"); // ✅
isValidUrl("ftp://example.com"); // ❌ (not http/https)
isValidUrl("not a url"); // ❌
URL Validation in Python
from urllib.parse import urlparse
import re
def is_valid_url(url: str) -> bool:
"""Validate URL using urlparse (recommended)"""
try:
result = urlparse(url)
return all([result.scheme in ('http', 'https'),
result.netloc,
'.' in result.netloc])
except:
return False
def is_valid_url_regex(url: str) -> bool:
"""Regex-based URL validation"""
pattern = r'^https?://[\w.-]+\.\w{2,}(/\S*)?$'
return bool(re.match(pattern, url))
# Extracting URLs from text
def extract_urls(text: str) -> list:
pattern = r'https?://[\w.-]+\.\w{2,}(?:/\S*)?'
return re.findall(pattern, text)
URL Validation in PHP
// Method 1: Built-in filter (recommended)
function isValidUrl($url): bool {
return filter_var($url, FILTER_VALIDATE_URL) !== false;
}
// Method 2: Regex (for http/https only)
function isValidHttpUrl($url): bool {
if (!filter_var($url, FILTER_VALIDATE_URL)) return false;
$scheme = parse_url($url, PHP_URL_SCHEME);
return in_array($scheme, ['http', 'https']);
}
// Specific patterns for URLs in content
$pattern = '/https?:\/\/(www\.)?[-\w]+\.\w{2,}(\/\S*)?/i';
preg_match_all($pattern, $content, $matches);
3. Phone Number Validation
Phone number validation is the hardest of the three. Phone number formats vary wildly between countries, and a "valid" pattern for one country is completely wrong for another. Here's how to handle it properly.
Patterns by Country & Format
| Format | Pattern | Valid examples |
|---|---|---|
| International (E.164) | ^\+[1-9]\d{6,14}$ | +12125551234, +442071838750 |
| Flexible international | ^\+?[\d\s\-().]{7,20}$ | +1 (212) 555-1234 |
| 🇲🇦 Morocco | ^(\+212|00212|0)[5-7]\d{8}$ | +212612345678, 0612345678 |
| 🇺🇸 USA | ^(\+1)?[\s.-]?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$ | (212) 555-1234, +12125551234 |
| 🇫🇷 France | ^(\+33|0)[1-9](\s?\d{2}){4}$ | +33612345678, 06 12 34 56 78 |
| 🇬🇧 UK | ^(\+44|0)\d{10}$ | +447911123456, 07911 123456 |
| 🇩🇪 Germany | ^(\+49|0)\d{10,11}$ | +4917612345678 |
| Digits only (any length) | ^\d{7,15}$ | Stripped of formatting |
Phone Validation in JavaScript — With Auto-Formatting
// Validate international phone (E.164 format)
function isValidPhone(phone) {
const e164 = /^\+[1-9]\d{6,14}$/;
return e164.test(phone);
}
// Moroccan phone numbers specifically
function isValidMoroccanPhone(phone) {
const pattern = /^(\+212|00212|0)[5-7]\d{8}$/;
return pattern.test(phone.replace(/[\s\-]/g, ''));
}
// Normalize before validating (remove spaces, dashes, parentheses)
function normalizePhone(phone) {
return phone.replace(/[\s\-().]/g, '');
}
const raw = "+212 06-12-34-56-78";
const normalized = normalizePhone(raw); // → "+212061234568"
isValidMoroccanPhone(normalized); // → true
Phone Validation in Python
import re
def normalize_phone(phone: str) -> str:
"""Remove spaces, dashes, parentheses"""
return re.sub(r'[\s\-().]', '', phone)
def is_valid_international(phone: str) -> bool:
phone = normalize_phone(phone)
return bool(re.match(r'^\+[1-9]\d{6,14}$', phone))
def is_valid_moroccan(phone: str) -> bool:
phone = normalize_phone(phone)
return bool(re.match(r'^(\+212|00212|0)[5-7]\d{8}$', phone))
# Using phonenumbers library (pip install phonenumbers) — most accurate
import phonenumbers
def is_valid_phone_lib(phone: str, country: str = "MA") -> bool:
try:
parsed = phonenumbers.parse(phone, country)
return phonenumbers.is_valid_number(parsed)
except:
return False
phonenumbers. JavaScript: libphonenumber-js. PHP: giggsey/libphonenumber-for-php. These libraries handle all country formats correctly — no regex required.
4. Combined Form Validation
Real-world example: validating a complete registration form with all three fields in JavaScript.
const validators = {
email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
url: /^https?:\/\/(www\.)?[-\w]+\.\w{2,}(\/\S*)?$/i,
phone: /^\+?[\d\s\-().]{7,20}$/,
};
function validateForm(data) {
const errors = {};
if (!validators.email.test(data.email?.trim())) {
errors.email = 'Please enter a valid email address';
}
if (data.website && !validators.url.test(data.website?.trim())) {
errors.website = 'Please enter a valid URL (include http:// or https://)';
}
if (data.phone && !validators.phone.test(data.phone?.trim())) {
errors.phone = 'Please enter a valid phone number';
}
return {
isValid: Object.keys(errors).length === 0,
errors
};
}
// Usage
const result = validateForm({
email: "user@example.com",
website: "https://youkip.com",
phone: "+212612345678"
});
console.log(result.isValid); // → true
5. Common Validation Mistakes
Patterns that reject user+tag@example.com or name@subdomain.example.co.uk are too strict. Both are perfectly valid.
"+212 06-12-34-56-78" and "+212061234568" are the same number. Always strip spaces, dashes, and parentheses before running your regex.
PHP's built-in FILTER_VALIDATE_URL handles far more edge cases than a hand-written pattern. Use it.
A trailing space in "user@example.com " will fail most email patterns. Always .trim() or strip() before validating.
nonexistent@totallyrealcompany.com passes email regex. It doesn't exist. For email marketing or critical communications, add a deliverability check via API.
6. Test Your Patterns Before Deploying
Every pattern in this guide was tested in YouKip Regex Tester Ultra — the free online tool that runs your pattern across 8 languages simultaneously, directly in your browser.
⚡ Test Your Validation Patterns Now
Paste any pattern from this guide. Test it against real input in JavaScript, Python, PHP, and Go simultaneously. 100% free, client-side — nothing sent to any server.
Open Regex Tester → FreeGet the Complete Regex Cheat Sheet (Free PDF)
50 patterns including email, URL, phone and more — printable PDF, dark mode.
⬇️ Download Free PDFLast updated: May 2026. All patterns tested across JavaScript, Python, PHP, and Go. Test every pattern against your specific use case before deploying to production — edge cases exist in every regex.