Master URL validation in JavaScript using regex patterns. Learn protocols, domains, paths, query strings with practical code examples.
Introduction
URL validation is an essential part of web development. Whenever you build forms, accept user input, or work with APIs — URL validation is necessary. In this tutorial, we will learn URL validation from basic to production-ready patterns using JavaScript regex.
A URL (Uniform Resource Locator) follows a structured format. With regex, we can validate each part individually or collectively. We will cover basic patterns through to production-ready solutions.
Is guide mein aap seekhenge:
- Matching different parts of a URL using regex
- Basic se advanced URL patterns
- Real-world form validation examples
- Common mistakes and how to avoid them
- Interview mein puche jaane wale questions
URL Regex Patterns
Pattern 1: Basic URL Validation
The simplest pattern that checks whether a URL starts with http or https:
const basicURLRegex = /^https?:\/\/.+/;
console.log(basicURLRegex.test("https://example.com"));
console.log(basicURLRegex.test("http://test.org"));
console.log(basicURLRegex.test("ftp://files.com"));
console.log(basicURLRegex.test("example.com"));
Pattern 2: Full URL with Named Groups
This pattern captures each part of the URL separately using named groups:
const fullURLRegex = /^(?<protocol>https?):\/\/(?<domain>(?:www\.)?[a-zA-Z0-9-]+\.[a-zA-Z]{2,})(?::(?<port>\d{1,5}))?(?<path>\/[^\s?#]*)?(?:\?(?<query>[^\s#]*))?(?:#(?<hash>[^\s]*))?$/;
const url = "https://www.example.com:8080/products/list?category=js&sort=new#top";
const match = url.match(fullURLRegex);
if (match) {
console.log("Protocol:", match.groups.protocol);
console.log("Domain:", match.groups.domain);
console.log("Port:", match.groups.port);
console.log("Path:", match.groups.path);
console.log("Query:", match.groups.query);
console.log("Hash:", match.groups.hash);
}
Protocol: https
Domain: www.example.com
Port: 8080
Path: /products/list
Query: category=js&sort=new
Hash: top
Pattern 3: Domain Only Validation
To validate only the domain name without the protocol:
const domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
console.log(domainRegex.test("example.com"));
console.log(domainRegex.test("sub.example.co.in"));
console.log(domainRegex.test("-invalid.com"));
console.log(domainRegex.test("no_underscore.com"));
console.log(domainRegex.test("a.b"));
true
true
false
false
true
Pattern 4: URL with Optional Path and Query
const flexibleURLRegex = /^https?:\/\/[a-zA-Z0-9-]+(\.[a-zA-Z]{2,})+(\:\d{1,5})?(\/[^\s]*)?$/;
console.log(flexibleURLRegex.test("https://google.com"));
console.log(flexibleURLRegex.test("http://api.example.com:3000/v1/users"));
console.log(flexibleURLRegex.test("https://shop.co.uk/products?id=123"));
console.log(flexibleURLRegex.test("not-a-url"));
Code Examples
Example 1: Basic URL Checker Function
function isValidURL(str) {
const urlPattern = /^https?:\/\/(www\.)?[a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/;
return urlPattern.test(str);
}
console.log(isValidURL("https://www.google.com"));
console.log(isValidURL("http://example.org/path"));
console.log(isValidURL("https://sub.domain.co.in/page?q=1"));
console.log(isValidURL("htp://invalid.com"));
console.log(isValidURL("just some text"));
true
true
true
false
false
Example 2: URL Validation With and Without WWW
function validateURLWithOptionalWWW(url) {
const pattern = /^https?:\/\/(www\.)?[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z]{2,})+\/?.*$/;
return pattern.test(url);
}
const testURLs = [
"https://www.example.com",
"https://example.com",
"http://www.test.org/page",
"http://test.org/page",
"https://blog.example.com",
"www.example.com"
];
testURLs.forEach(url => {
console.log(`${url} → ${validateURLWithOptionalWWW(url)}`);
});
https://www.example.com → true
https://example.com → true
http://www.test.org/page → true
http://test.org/page → true
https://blog.example.com → true
www.example.com → false
Example 3: Validate Specific Domains Only
function isAllowedDomain(url, allowedDomains) {
const domainPattern = /^https?:\/\/(?:www\.)?([^\/\s:]+)/;
const match = url.match(domainPattern);
if (!match) return { valid: false, domain: null };
const domain = match[1].toLowerCase();
const isAllowed = allowedDomains.some(d =>
domain === d || domain.endsWith('.' + d)
);
return { valid: isAllowed, domain };
}
const allowed = ["github.com", "stackoverflow.com", "mdn.org"];
console.log(isAllowedDomain("https://github.com/user/repo", allowed));
console.log(isAllowedDomain("https://docs.github.com/en", allowed));
console.log(isAllowedDomain("https://evil-site.com/phish", allowed));
console.log(isAllowedDomain("https://stackoverflow.com/questions", allowed));
{ valid: true, domain: "github.com" }
{ valid: true, domain: "docs.github.com" }
{ valid: false, domain: "evil-site.com" }
{ valid: true, domain: "stackoverflow.com" }function extractURLParts(url) {
const regex = /^(?<protocol>https?):\/\/(?<host>[^\/\s:]+)(?::(?<port>\d+))?(?<path>\/[^?\s#]*)?(?:\?(?<queryString>[^#\s]*))?(?:#(?<fragment>\S*))?$/;
const match = url.match(regex);
if (!match) return null;
const { protocol, host, port, path, queryString, fragment } = match.groups;
// Parse query string into object
const params = {};
if (queryString) {
queryString.split("&").forEach(pair => {
const [key, value] = pair.split("=");
params[decodeURIComponent(key)] = decodeURIComponent(value || "");
});
}
return { protocol, host, port: port || null, path: path || "/", params, fragment: fragment || null };
}
const result = extractURLParts("https://shop.example.com:443/products/search?category=electronics&brand=samsung&page=2#results");
console.log(JSON.stringify(result, null, 2));
{
"protocol": "https",
"host": "shop.example.com",
"port": "443",
"path": "/products/search",
"params": {
"category": "electronics",
"brand": "samsung",
"page": "2"
},
"fragment": "results"
}Example 5: new URL() Constructor Approach vs Regex
// Method 1: Using URL Constructor (recommended for most cases)
function isValidURLConstructor(str) {
try {
const url = new URL(str);
return url.protocol === "http:" || url.protocol === "https:";
} catch (e) {
return false;
}
}
// Method 2: Using Regex (more control over format)
function isValidURLRegex(str) {
const regex = /^https?:\/\/[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z]{2,})+([\/\w\-.~:?#[\]@!$&'()*+,;=%]*)$/;
return regex.test(str);
}
const testCases = [
"https://example.com",
"http://localhost:3000",
"https://192.168.1.1/admin",
"https://example",
"javascript:alert(1)",
"https://valid-site.com/path?q=hello"
];
console.log("URL".padEnd(35), "Constructor".padEnd(14), "Regex");
console.log("-".repeat(60));
testCases.forEach(url => {
const c = isValidURLConstructor(url);
const r = isValidURLRegex(url);
console.log(url.padEnd(35), String(c).padEnd(14), r);
});
URL Constructor Regex
------------------------------------------------------------
https://example.com true true
http://localhost:3000 true false
https://192.168.1.1/admin true false
https://example true false
javascript:alert(1) false false
https://valid-site.com/path?q=hello true true
function validateFormURL(input) {
const errors = [];
// Check if empty
if (!input || input.trim() === "") {
return { valid: false, errors: ["URL field is required"] };
}
// Check protocol
if (!/^https?:\/\//i.test(input)) {
errors.push("URL must start with http:// or https://");
}
// Check for spaces
if (/\s/.test(input)) {
errors.push("URL cannot contain spaces");
}
// Check domain format
if (!/^https?:\/\/[a-zA-Z0-9][a-zA-Z0-9-.]*\.[a-zA-Z]{2,}/i.test(input)) {
errors.push("Invalid domain format");
}
// Check for double dots
if (/\.\./.test(input)) {
errors.push("URL cannot contain consecutive dots");
}
// Check total length
if (input.length > 2048) {
errors.push("URL exceeds maximum length (2048 characters)");
}
// Final regex validation
const fullPattern = /^https?:\/\/[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}(:\d{1,5})?(\/[^\s]*)?$/;
if (errors.length === 0 && !fullPattern.test(input)) {
errors.push("URL format is invalid");
}
return { valid: errors.length === 0, errors };
}
console.log(validateFormURL("https://www.example.com/page"));
console.log(validateFormURL("http://my site.com"));
console.log(validateFormURL("example.com"));
console.log(validateFormURL("https://valid.co.in:8080/api/v1"));
console.log(validateFormURL(""));
{ valid: true, errors: [] }
{ valid: false, errors: ["URL cannot contain spaces", "Invalid domain format"] }
{ valid: false, errors: ["URL must start with http:// or https://", "Invalid domain format"] }
{ valid: true, errors: [] }
{ valid: false, errors: ["URL field is required"] }Example 7: URL Sanitizer and Validator Combined
function sanitizeAndValidateURL(rawInput) {
// Step 1: Trim whitespace
let url = rawInput.trim();
// Step 2: Add protocol if missing
if (/^[a-zA-Z0-9]/.test(url) && !url.match(/^[a-zA-Z]+:\/\//)) {
url = "https://" + url;
}
// Step 3: Lowercase the protocol and domain
url = url.replace(/^(https?:\/\/)([^\/]+)/, (match, proto, domain) => {
return proto.toLowerCase() + domain.toLowerCase();
});
// Step 4: Remove trailing slash if no path
url = url.replace(/^(https?:\/\/[^\/]+)\/$/, "$1");
// Step 5: Validate final URL
const isValid = /^https?:\/\/[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z]{2,})+([\/\S]*)?$/.test(url);
return {
original: rawInput,
sanitized: url,
valid: isValid
};
}
console.log(sanitizeAndValidateURL(" WWW.Example.COM "));
console.log(sanitizeAndValidateURL("HTTPS://BLOG.SITE.ORG/Post"));
console.log(sanitizeAndValidateURL("example.com/"));
console.log(sanitizeAndValidateURL("not a valid url at all!"));
{ original: " WWW.Example.COM ", sanitized: "https://www.example.com", valid: true }
{ original: "HTTPS://BLOG.SITE.ORG/Post", sanitized: "https://blog.site.org/Post", valid: true }
{ original: "example.com/", sanitized: "https://example.com", valid: true }
{ original: "not a valid url at all!", sanitized: "https://not a valid url at all!", valid: false }
Regex vs URL Constructor — Detailed Comparison
| Feature | Regex Approach | URL Constructor |
|---|
| Browser Support | All browsers | Modern browsers (not in IE) |
| Strictness | Customizable | Lenient (allows https://example) |
| Performance | Fast for simple checks | Object creation overhead |
| Custom Rules | Easy to add domain restrictions | Harder to restrict |
| Error Messages | Can give specific errors | Only valid/invalid |
| IP Address URLs | Need explicit pattern | Automatically handles |
| Internationalized URLs | Complex to handle | Built-in support |
| XSS Prevention | Must explicitly block javascript: | Check protocol after parsing |
| Best For | Form validation, specific format enforcement | General URL parsing, API work |
Recommendation: In production applications use a combination of both — new URL() for parsing and regex for specific format enforcement.
// Best Practice: Combined approach
function productionURLValidator(input) {
// First: regex for format enforcement
const formatCheck = /^https?:\/\/[^\s]+$/.test(input);
if (!formatCheck) return false;
// Second: URL constructor for parsing validity
try {
const parsed = new URL(input);
// Additional checks
if (!["http:", "https:"].includes(parsed.protocol)) return false;
if (!parsed.hostname.includes(".")) return false;
return true;
} catch {
return false;
}
}
console.log(productionURLValidator("https://example.com/path"));
console.log(productionURLValidator("https://localhost"));
console.log(productionURLValidator("javascript:alert(1)"));
console.log(productionURLValidator("https://valid.site.com"));
🚫 Common Mistakes
Mistake 1: Forgetting to Escape Forward Slashes
// ❌ WRONG — slashes not escaped
const wrong = /^https?://.+/; // SyntaxError!
// ✅ CORRECT — slashes escaped
const correct = /^https?:\/\/.+/;
console.log(correct.test("https://example.com"));
Mistake 2: Not Anchoring the Pattern
// ❌ WRONG — no anchors, matches partial strings
const noAnchors = /https?:\/\/[a-z]+\.[a-z]+/;
console.log(noAnchors.test("MALICIOUS https://evil.com INJECTION"));
// ✅ CORRECT — anchored with ^ and $
const anchored = /^https?:\/\/[a-z]+\.[a-z]+$/;
console.log(anchored.test("MALICIOUS https://evil.com INJECTION"));
Mistake 3: Not Handling Case Sensitivity
// ❌ WRONG — won't match uppercase
const caseSensitive = /^https?:\/\/[a-z0-9.-]+\.[a-z]{2,}$/;
console.log(caseSensitive.test("https://Example.COM"));
// ✅ CORRECT — use 'i' flag
const caseInsensitive = /^https?:\/\/[a-z0-9.-]+\.[a-z]{2,}$/i;
console.log(caseInsensitive.test("https://Example.COM"));
Mistake 4: Allowing javascript: Protocol (XSS Vulnerability)
// ❌ WRONG — allows any protocol
const anyProtocol = /^\w+:\/\/.+$/;
console.log(anyProtocol.test("javascript://alert(1)"));
// ✅ CORRECT — explicitly allow only http/https
const safeProtocol = /^https?:\/\/.+$/;
console.log(safeProtocol.test("javascript://alert(1)"));
Mistake 5: Overly Strict TLD Validation
// ❌ WRONG — only allows 3-letter TLDs (misses .io, .co, .technology)
const strictTLD = /^https?:\/\/[a-z0-9.-]+\.[a-z]{3}$/i;
console.log(strictTLD.test("https://example.io"));
console.log(strictTLD.test("https://site.technology"));
// ✅ CORRECT — allow 2+ character TLDs
const flexibleTLD = /^https?:\/\/[a-z0-9.-]+\.[a-z]{2,}$/i;
console.log(flexibleTLD.test("https://example.io"));
console.log(flexibleTLD.test("https://site.technology"));
Mistake 6: Catastrophic Backtracking
// ❌ WRONG — nested quantifiers can cause catastrophic backtracking
// const dangerous = /^https?:\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)*$/;
// ✅ CORRECT — use atomic-style patterns, avoid nested repetition
const safe = /^https?:\/\/[\w][\w.-]*\.[a-z]{2,}(\/[^\s]*)?$/i;
console.log(safe.test("https://example.com/path/to/resource?key=value"));
🎯 Key Takeaways
- Use
new URL() for simple checks — it's the browser's built-in parser and handles edge cases.
- Use regex for specific format enforcement — like allowing only HTTPS, restricting to specific domains, or validating query parameters.
- Always use
^ and $ anchors — without anchors, regex can match partial strings, which is a security risk.
- Explicitly block the
javascript: protocol — this is critical for preventing XSS attacks.
- Don't forget the case-insensitive flag (
i) — protocol and domain in URLs are case-insensitive.
- Combine both approaches in production — regex format check + URL constructor parsing = robust validation.
- Don't overly restrict TLDs — new TLDs are constantly being added (.app, .dev, .technology, etc.).
- Avoid nested quantifiers — they can cause catastrophic backtracking and freeze the app.
- Always sanitize user input first — trim, lowercase protocol/domain, add missing protocol if needed.
- Make sure to test edge cases — IP addresses, ports, international domains, very long URLs, special characters.
❓ Interview Questions
Q1: What is the difference between Regex and URL Constructor for URL validation?
Answer: Regex gives you exact format control — you can specify which characters, protocols, or domains are allowed. The URL Constructor (new URL()) is the browser's built-in parser and handles edge cases, but gives less granular control over the exact format.
Q2: How do you prevent XSS in URL validation?
Answer: To prevent XSS: (1) Only allow http: and https: protocols — block javascript:, data:, vbscript:. (2) Sanitize the URL before inserting into DOM. (3) Use Content Security Policy headers.
function isSafeURL(url) {
try {
const parsed = new URL(url);
return ["http:", "https:"].includes(parsed.protocol);
} catch {
return false;
}
}
console.log(isSafeURL("javascript:alert('xss')"));
console.log(isSafeURL("https://safe-site.com"));
Q3: What is catastrophic backtracking and how do you avoid it in URL regex?
Answer: Catastrophic backtracking occurs when the regex engine has to try an exponential number of paths — usually due to nested quantifiers (e.g., (a+)+). In URL regex, this happens when the domain or path section has overly greedy patterns. Prevention: Use atomic groups, possessive quantifiers, or simpler non-backtracking patterns.
Q4: How do you validate International Domain Names (IDN) with regex?
Answer: International domain names (like münchen.de or 例え.jp) are punycode encoded (e.g., xn--mnchen-3ya.de). Validating Unicode domains directly with regex is complex. The best approach is to first convert to punycode using a library, then validate.
Q5: What is the best approach for URL validation in a production application?
Answer: For production-ready URL validation, follow this strategy:
function validateURL(input, options = {}) {
const { requireHTTPS = false, allowedDomains = null, maxLength = 2048 } = options;
// Step 1: Basic sanitization
const trimmed = input.trim();
if (trimmed.length > maxLength) return { valid: false, reason: "Too long" };
// Step 2: Protocol check with regex
const protocolRegex = requireHTTPS ? /^https:\/\// : /^https?:\/\//;
if (!protocolRegex.test(trimmed)) return { valid: false, reason: "Invalid protocol" };
// Step 3: Parse with URL constructor
let parsed;
try { parsed = new URL(trimmed); }
catch { return { valid: false, reason: "Malformed URL" }; }
// Step 4: Domain whitelist check
if (allowedDomains && !allowedDomains.some(d => parsed.hostname.endsWith(d))) {
return { valid: false, reason: "Domain not allowed" };
}
return { valid: true, parsed };
}
console.log(validateURL("https://example.com", { requireHTTPS: true }));
console.log(validateURL("http://example.com", { requireHTTPS: true }));
{ valid: true, parsed: URL { href: "https://example.com/", ... } }
{ valid: false, reason: "Invalid protocol" }
📝 FAQ
Q: Is 100% accurate URL validation possible with regex?
A: No, fully RFC 3986 compliant URL validation with regex alone is practically impossible because the URL specification is very complex. In real-world applications, a regex that covers 95%+ of cases is sufficient. For full compliance, use the new URL() constructor.
Q: Does new URL() work in all browsers?
A: new URL() is supported in all modern browsers (Chrome, Firefox, Safari, Edge). It's not supported in Internet Explorer, but IE is practically dead in 2026. It's also fully supported in Node.js.
Q: What is the most common security vulnerability in URL validation?
A: The most common vulnerability is allowing javascript: protocol URLs. If you use user input in an anchor tag's href or in window.location without validation, attackers can inject malicious scripts. Always whitelist allowed protocols (http, https, mailto).
Q: How do you validate URLs with IP addresses using regex?
A: For IP address URLs, you need to add a separate pattern:
const ipURLRegex = /^https?:\/\/(\d{1,3}\.){3}\d{1,3}(:\d{1,5})?(\/\S*)?$/;
console.log(ipURLRegex.test("http://192.168.1.1:8080/api"));
console.log(ipURLRegex.test("https://10.0.0.1/admin"));
Note: This pattern doesn't validate IP ranges (it would allow values over 255). For complete validation, check each octet separately or use the URL constructor.
Q: How do you handle localhost URLs in development?
A: To allow localhost and custom ports in a development environment:
const devURLRegex = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d{1,5})?(\/\S*)?$/;
console.log(devURLRegex.test("http://localhost:3000/api"));
console.log(devURLRegex.test("http://127.0.0.1:8080"));
// Production + Dev combined
function isValidEnvURL(url, isDev = false) {
if (isDev && /^https?:\/\/(localhost|127\.0\.0\.1)/.test(url)) return true;
return /^https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/.test(url);
}
console.log(isValidEnvURL("http://localhost:3000", true));
console.log(isValidEnvURL("http://localhost:3000", false));