How to Fix ERR_INVALID_URL in Node.js
The ERR_INVALID_URL error in Node.js occurs when the URL constructor or url.parse() receives a string that is not a valid URL. Common causes include missing protocol, unencoded special characters, and malformed percent-encoding. Fix it by validating input and encoding special characters before parsing.
What Causes ERR_INVALID_URL?
The ERR_INVALID_URL error (formally TypeError [ERR_INVALID_URL]: Invalid URL) is thrown by Node.js when the URL constructor or the legacy url.parse() function receives a string that cannot be parsed as a valid URL. This error is common when processing user input, parsing configuration files, or handling URLs from external sources.
The URL constructor follows the WHATWG URL Standard, which is stricter than the older RFC-based parsing. A string must include a valid scheme (like https:), a valid authority (hostname), and properly formatted path and query components to be accepted as a valid URL.
// This throws ERR_INVALID_URL
try {
const url = new URL('not-a-url');
} catch (err) {
console.log(err.code); // 'ERR_INVALID_URL'
console.log(err.message); // 'Invalid URL: not-a-url'
console.log(err.input); // 'not-a-url'
}Common Causes and Fixes
Cause 1: Missing protocol/scheme. The URL constructor requires a scheme like https:// or http://. Strings like example.com/path or www.example.com will fail.
// FAILS: no protocol
new URL('example.com/path'); // ERR_INVALID_URL
// FIX: add the protocol
new URL('https://example.com/path'); // Works!
// FIX: add protocol if missing
function ensureProtocol(urlString) {
if (!/^https?:\/\//i.test(urlString)) {
return 'https://' + urlString;
}
return urlString;
}
new URL(ensureProtocol('example.com/path')); // Works!Cause 2: Unencoded special characters. Characters like spaces, curly braces, pipes, and certain Unicode characters are not allowed in URLs without encoding.
// FAILS: unencoded spaces and special characters
new URL('https://example.com/my file.pdf'); // ERR_INVALID_URL
new URL('https://example.com/path?q=a b'); // May fail in some versions
// FIX: encode the problematic parts
const filename = encodeURIComponent('my file.pdf');
new URL('https://example.com/' + filename); // Works!
// FIX: use encodeURI for a complete URL with spaces
const rawUrl = 'https://example.com/my file.pdf';
new URL(encodeURI(rawUrl)); // Works!Cause 3: Malformed percent-encoding. If a URL contains a percent sign that is not followed by exactly two hexadecimal digits, the parser rejects it.
// FAILS: malformed percent encoding
new URL('https://example.com/100%done'); // ERR_INVALID_URL
new URL('https://example.com/path?q=50%'); // ERR_INVALID_URL
// FIX: encode the lone percent sign as %25
function fixPercentSigns(urlString) {
return urlString.replace(/%(?![0-9A-Fa-f]{2})/g, '%25');
}
new URL(fixPercentSigns('https://example.com/100%done'));
// Works! URL becomes https://example.com/100%25doneCause 4: Empty or null input. Passing an empty string, null, or undefined to the URL constructor also triggers this error.
// FAILS: empty or null input
new URL(''); // ERR_INVALID_URL
new URL(null); // ERR_INVALID_URL
new URL(undefined); // ERR_INVALID_URL
// FIX: validate input before parsing
function parseUrl(input) {
if (!input || typeof input !== 'string') {
return null;
}
try {
return new URL(input);
} catch {
return null;
}
}Cause 5: Relative URLs without a base. The URL constructor treats the first argument as an absolute URL by default. Relative URLs like /path/to/pagerequire a base URL as the second argument.
// FAILS: relative URL without base
new URL('/api/users'); // ERR_INVALID_URL
// FIX: provide a base URL
new URL('/api/users', 'https://example.com');
// Works! → https://example.com/api/users
// Useful pattern for API clients
const BASE_URL = 'https://api.example.com';
const endpoint = new URL('/v2/users?page=1', BASE_URL);
console.log(endpoint.href);
// "https://api.example.com/v2/users?page=1"How to Validate URLs Before Parsing
The most reliable way to validate a URL in Node.js is to use the URL constructor inside a try-catch block. There is no built-in URL.isValid() method (as of Node.js 22), so catching the error is the standard approach.
// Simple URL validator
function isValidUrl(string) {
try {
new URL(string);
return true;
} catch {
return false;
}
}
console.log(isValidUrl('https://example.com')); // true
console.log(isValidUrl('not-a-url')); // false
console.log(isValidUrl('')); // false
// Validator with allowed protocols
function isValidHttpUrl(string) {
try {
const url = new URL(string);
return url.protocol === 'http:' || url.protocol === 'https:';
} catch {
return false;
}
}
console.log(isValidHttpUrl('https://example.com')); // true
console.log(isValidHttpUrl('ftp://example.com')); // false
console.log(isValidHttpUrl('javascript:alert(1)')); // falseSafe URL Parsing Pattern
Here is a robust URL parsing pattern that handles all common error cases and returns a parsed URL object or a meaningful error.
class UrlParser {
static parse(input, base) {
// Validate input
if (!input || typeof input !== 'string') {
return { ok: false, error: 'Input must be a non-empty string' };
}
// Trim whitespace
const trimmed = input.trim();
// Fix common issues
let urlString = trimmed;
// Add protocol if missing
if (/^[a-zA-Z0-9]/.test(urlString) && !urlString.includes('://')) {
urlString = 'https://' + urlString;
}
// Fix lone percent signs
urlString = urlString.replace(/%(?![0-9A-Fa-f]{2})/g, '%25');
try {
const url = base ? new URL(urlString, base) : new URL(urlString);
// Optional: restrict to safe protocols
if (!['http:', 'https:'].includes(url.protocol)) {
return { ok: false, error: 'Unsupported protocol: ' + url.protocol };
}
return { ok: true, url };
} catch (err) {
return { ok: false, error: err.message };
}
}
}
// Usage
const result = UrlParser.parse('example.com/path?q=hello world');
if (result.ok) {
console.log(result.url.href);
} else {
console.error(result.error);
}Prevention Best Practices
Following these practices will help you avoid ERR_INVALID_URL errors in your Node.js applications and build more resilient URL handling.
- Always wrap URL parsing in try-catch. Never assume a URL string is valid, especially when it comes from user input, environment variables, databases, or external APIs.
- Use the URL API instead of string concatenation. Build URLs using the
URLconstructor andURLSearchParamsrather than concatenating strings. The API handles encoding automatically. - Validate environment variables at startup. If your application reads URLs from environment variables or config files, validate them when the application starts, not when they are first used.
- Encode user input before embedding in URLs. Always use
encodeURIComponent()for query parameter values andencodeURI()for complete URLs provided by users. - Use a URL builder for complex cases. For building URLs with many dynamic parts, create a helper function or class that handles encoding consistently.
- Log the original input on errors. When you catch an
ERR_INVALID_URLerror, log the input that caused it (sanitized if it might contain sensitive data) to help debug the issue.
// Good practice: validate config URLs at startup
const requiredUrls = ['API_BASE_URL', 'AUTH_SERVER_URL', 'WEBHOOK_URL'];
for (const envVar of requiredUrls) {
const value = process.env[envVar];
if (!value) {
throw new Error(envVar + ' environment variable is required');
}
try {
new URL(value);
} catch {
throw new Error(envVar + ' is not a valid URL: ' + value);
}
}
// Good practice: use URL API for building URLs
function buildApiUrl(endpoint, params) {
const url = new URL(endpoint, process.env.API_BASE_URL);
for (const [key, value] of Object.entries(params)) {
url.searchParams.set(key, String(value));
}
return url.toString();
}
const searchUrl = buildApiUrl('/api/search', {
q: 'Node.js & Express',
page: 1
});
// "https://api.example.com/api/search?q=Node.js+%26+Express&page=1"