TypeError: Failed to construct 'URL': Invalid URL

Quick answer

The string passed to new URL(...) isn't a complete, valid absolute URL — usually a relative path with no base, a missing protocol, or genuinely malformed input. Fix it with the two-argument form new URL(path, base) for relative paths, or wrap construction in try/catch when the string comes from user input.

The exact error string

new URL("/api/users");
// Uncaught TypeError: Failed to construct 'URL': Invalid URL

new URL("example.com");
// Uncaught TypeError: Failed to construct 'URL': Invalid URL
//   -> no protocol, so this isn't a recognizable absolute URL

Why this throws instead of returning something empty

Unlike most JavaScript string handling, the URL constructor validates its input against the URL spec immediately and synchronously — there's no "empty URL" fallback the way Number("abc") gives you NaN. If the input isn't a parseable absolute URL (and no base was given to resolve a relative one against), it throws right there, which is exactly why this needs a try/catch around any URL coming from outside your control.

Cause 1: a relative path with no base

new URL("/api/users");
// ❌ relative path — no scheme, no host

new URL("/api/users", "https://example.com");   // ✅ two-argument form
// -> https://example.com/api/users

new URL("/api/users", window.location.origin);   // ✅ resolve against the current page

Cause 2: a missing protocol

new URL("example.com");
// ❌ looks like a bare hostname, not a full URL

new URL("https://example.com");   // ✅ explicit protocol

// or detect and normalize user-typed input:
function toAbsoluteUrl(input) {
  return /^https?:\/\//i.test(input) ? input : "https://" + input;
}
new URL(toAbsoluteUrl("example.com"));   // ✅

Cause 3: unvalidated user input

function isValidUrl(value) {
  try {
    new URL(value);
    return true;
  } catch {
    return false;   // ✅ bad input is expected, not exceptional — handle it, don't crash
  }
}

if (isValidUrl(userInput)) {
  const url = new URL(userInput);
  // ... use it
} else {
  showError("Please enter a valid URL, including https://");
}

Cause 4: a broken template literal

const base = "https://api.example.com";   // no trailing slash
const path = "users";
new URL(`${base}/${path}`);   // ✅ https://api.example.com/users

const endpoint = undefined;
new URL(`${base}/${endpoint}`);
// ❌ builds "https://api.example.com/undefined" — technically parses,
//    but is not the URL you meant; log the built string to catch this

Common variants at a glance

InputWhy it failsFix
/pathrelative, no basenew URL(path, base)
example.comno protocolprepend https://
arbitrary user textnot a URL at alltry/catch or validate first
${base}${path}missing separator / undefined variablelog the built string before constructing
empty stringnothing to parsecheck for empty before constructing

Debugging checklist

Frequently Asked Questions

What does 'Failed to construct URL: Invalid URL' mean?

The string you passed to new URL(...) is not a valid absolute URL, and no second base-URL argument was given to resolve it against. Unlike most string operations, the URL constructor validates its input immediately and throws synchronously if it can't parse a complete URL from what it was given.

Why does a relative path throw this?

new URL('/api/users') fails because a relative path has no scheme or host — the constructor needs a full, absolute URL unless you also provide a base as the second argument: new URL('/api/users', 'https://example.com'). The two-argument form is exactly how the browser resolves relative links against a page's own URL.

Why does a missing protocol cause this?

new URL('example.com') throws because without http:// or https://, the string looks like a relative path (or an opaque scheme-less value), not an absolute URL. Prepend the protocol explicitly, or detect and add it before constructing: value.startsWith('http') ? value : 'https://' + value.

Why does user input break this?

A text field where users type a URL will inevitably get malformed input — spaces, missing protocol, extra characters, or a value that isn't a URL at all. Never call new URL(userInput) unguarded; wrap it in try/catch or validate the string first, since a bad value is expected user behavior, not an exceptional case.

Why does a template literal produce an invalid URL?

A common bug is an unresolved template variable or a missing separator: new URL(`${base}${path}`) can produce something like 'https://api.example.compath' if base doesn't end in a slash, or embed "undefined" if a variable wasn't set. Log the built string before passing it to new URL to catch this.

How do I validate a URL without throwing?

Wrap the construction in try/catch and treat a thrown error as "not a valid URL" rather than letting it crash the page: function isValidUrl(s) { try { new URL(s); return true; } catch { return false; } }. This is the standard, spec-correct way to validate a URL string in JavaScript.

Building or decoding URLs?

Encode/decode query strings and URL components entirely in your browser — nothing is uploaded to a server.

URL Encoder & Decoder All Error References HTTP Status Codes
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds privacy-first developer tools at JSON Dev Tools.