Use URL.canParse for safer URL handling
Published September 3, 2027
export const parseValidUrls = (candidates: string[], base?: string): URL[] =>
candidates.filter((candidate) => URL.canParse(candidate, base)).map((candidate) => new URL(candidate, base));
export const isSameOriginRedirect = (target: string, currentOrigin: string): boolean => {
if (!URL.canParse(target, currentOrigin)) return false;
return new URL(target, currentOrigin).origin === currentOrigin;
};What
URL.canParse(url, base?) is a static method that returns true or false for whether url (optionally resolved against base) is a parsable URL — the exact same parsing new URL() does, minus the object allocation and minus the thrown exception on failure.
Why it matters
Before this existed, checking "is this string a valid URL" meant wrapping new URL(candidate) in a try/catch and discarding the result — allocating a URL object you don't want, just to use its constructor as a validity check, and reaching for exception handling to do what's fundamentally a boolean question. That pattern is also easy to get subtly wrong: forgetting the base argument on a relative URL throws for a different reason than an actually-malformed URL, and try/catch swallows both identically unless you inspect the error. canParse separates the question ("would this parse?") from the action ("give me the parsed result"), so validation and construction are two explicit steps instead of one overloaded try/catch.
How it works
parseValidUrlsfilters a list of candidate strings down to the onesURL.canParseaccepts, then constructsURLobjects only for the ones known to succeed — no exceptions thrown in the happy path, no wasted allocations for the rejected ones.isSameOriginRedirectguardsnew URL()behind acanParsecheck first, then compares.originagainstcurrentOrigin— a realistic shape for validating a?redirect=query parameter before honoring it, which is exactly the kind of input an open-redirect vulnerability comes from.- Both functions pass
basestraight through tocanParseand tonew URL()identically —canParsereturningtrueis a guarantee that the same call tonew URL()will not throw, not just a loose approximation.
Gotchas
canParseanswers "is this syntactically a URL," not "is this URL safe to fetch, redirect to, or trust." A same-origin check likeisSameOriginRedirectis still necessary on top of it — a perfectly parsable URL can still point somewhere you don't want to send a user.- Passing
basechanges what counts as valid:URL.canParse("/path")isfalse(no base to resolve a relative path against), butURL.canParse("/path", "https://example.com")istrue. If you're validating strings that might be either absolute or relative, decide up front whether you're supplying a base. - Support arrived within the same few months across engines but at different exact versions: Chrome 120 (December 2023), Firefox 115 (July 2023), Safari 17 (September 2023) — safe to use unguarded in any project targeting current browsers, but check your minimum supported versions if you maintain an older baseline.
- It's available in Web Workers too, unlike some URL-adjacent APIs — no main-thread-only restriction here.
Related
new URL()in atry/catch— the pattern this replaces; still necessary in environments withoutcanParse, or when you want the parsed URL and the validity check in one step.URLPattern— for matching a URL against a route-like pattern rather than just checking parsability; a different question with a different API.