URL & URLSearchParams
Learn to parse, build, encode, decode, validate, and safely edit URLs with URL and URLSearchParams instead of fragile string concatenation.
- 01Read every URL partUse
new URL(input, base)to inspect protocol, host, path, query, and hash. - 02Build query strings safelyUse live
url.searchParamsmethods for filters, pages, and repeated keys. - 03Choose the right encoderCompare
encodeURI,encodeURIComponent, decoding,URL.canParse, andURL.parse.
URLs are not just strings
A URL looks like text, but it is really a structured address. It can name a scheme, a host, a port, a path, a query string, and a hash. JavaScript gives you two built-in helpers for this job: URL for the address and URLSearchParams for the form-like query string after ?.
This matters any time code builds a link, redirects a user, saves a return URL, or calls an API. String concatenation is easy until a user searches for rock & roll, a page name contains a space, or a filter appears twice. The browser and Node.js already know the URL rules, so let them do the paperwork.
new URL(input, base?) parses an address into safe, editable parts. url.searchParams is a live URLSearchParams object for reading and writing the query string.
If you write one long address on an envelope, the mail service still reads it in labeled chunks. URLs work the same way. The punctuation is not decoration; it tells the parser where one part ends and the next begins.
- In real life: Delivery service
- In JavaScript: protocol, like
https: - In real life: City and street
- In JavaScript: host, like
example.com - In real life: Apartment door
- In JavaScript: port, like
:8443 - In real life: Rooms inside
- In JavaScript: pathname, like
/docs/url - In real life: A note with questions
- In JavaScript: search, like
?q=url - In real life: A sticky tab in a book
- In JavaScript: hash, like
#examples
Where the analogy stops: Postal workers see every part of a postal address. A URL hash is different: browsers use it locally and do not send it to the server in an HTTP request.
If strings and Unicode are still fresh topics for you, keep the Strings and Unicode and strings lessons nearby. We will also use object conversion ideas from Object.keys, values and entries.
Parse with new URL()
INTERACTIVEThe constructor is strict on purpose. new URL(input) accepts an absolute URL. new URL(input, base) also accepts a relative input and resolves it against the base. If the result is not a valid URL, it throws a TypeError.
Once parsing succeeds, the object exposes normalized properties. href is the full address. origin is the scheme plus host and non-default port for HTTP(S). hostname uses punycode for internationalized domain names. port is the empty string when the URL uses the default port, like 443 for HTTPS. hash includes the leading #, but the hash is a browser-side fragment, not data sent to the server.
const url = new URL(input, base);console.log(url.href);console.log(url.protocol);console.log(url.hostname);console.log(url.searchParams.getAll("tag"));- href
https://example.com:8443/docs/page?tab=api#examples- origin
https://example.com:8443- protocol
https:- username
(empty)- password
(empty)- host
example.com:8443- hostname
example.com- port
8443- pathname
/docs/page- search
?tab=api- hash
#examples- tag values
(none)
Parsed successfully. URL.canParse says true. Checking URL.parse in your browser….
Try the Unicode preset: the domain changes to punycode in hostname, while the path and query are percent-encoded in href. Try the credentials preset too. Usernames and passwords can appear in URLs, but showing or logging them is usually a security smell in real applications.
Build queries with URLSearchParams
INTERACTIVEQuery strings are tiny forms in the URL. A key names a box, and a value fills it: ?q=boots&page=2. The same key can appear more than once, which is common for filters: ?tag=js&tag=web.
Instead of writing & and = by hand, fill in labeled boxes. URLSearchParams handles the special-character paperwork and serializes the form correctly.
- In real life: A box labeled Search
- In JavaScript:
set('q', value) - In real life: Several checked topics
- In JavaScript: repeated
append('tag', value)calls - In real life: Erasing a field
- In JavaScript:
delete('page') - In real life: Putting forms in a neat order
- In JavaScript:
sort()
Where the analogy stops: A paper form can keep two boxes with the same label visually separate. When you convert params to an object with Object.fromEntries, duplicate labels collapse and the last value wins.
The searchParams property on a URL is live. Change it and url.search plus url.href update immediately. Use set to replace one value, append for repeated keys, delete to remove a key, has to check for a key, get for the first value, and getAll for every value. Iteration yields [name, value] pairs.
const url = new URL("https://example.com/search");url.searchParams.set("q", query);for (const tag of tags) url.searchParams.append("tag", tag);url.searchParams.set("page", page);url.searchParams.sort();console.log(url.href);console.log([...url.searchParams]);- url.href
https://example.com/search?q=rock+%26+roll+%F0%9F%8E%B8&tag=music&tag=live+shows&page=2- toString()
q=rock+%26+roll+%F0%9F%8E%B8&tag=music&tag=live+shows&page=2- getAll("tag")
["music","live shows"]- has("page")
true- iteration
[["q","rock & roll 🎸"],["tag","music"],["tag","live shows"],["page","2"]]- new URLSearchParams(object)
q=rock+%26+roll+%F0%9F%8E%B8&page=2- Object.fromEntries
{"q":"rock & roll 🎸","tag":"live shows","page":"2"}
Live URL: https://example.com/search?q=rock+%26+roll+%F0%9F%8E%B8&tag=music&tag=live+shows&page=2
Notice the space rule: URLSearchParams serializes spaces as +, because query strings use the application/x-www-form-urlencoded tradition. It decodes those plus signs back to spaces when you read values through URLSearchParams.
Encoding and decoding
INTERACTIVEURL punctuation has meaning. A raw & separates query parameters, = separates a name from a value, ? starts the query, # starts the fragment, and / separates path segments. When those characters are user data, they must be packed so they are not mistaken for structure.
The URL parser is like a shipping clerk. If fragile punctuation is loose in the box, it may be read as part of the address. Encoding wraps it as percent escapes such as %26.
- In real life: Bubble-wrapping a glass
- In JavaScript: percent-encoding a space,
&,=,?,#, or emoji - In real life: Leaving address labels visible
- In JavaScript:
encodeURIkeeps URL punctuation - In real life: Wrapping one item completely
- In JavaScript:
encodeURIComponentprotects a single value or segment
Where the analogy stops: Encoding is not encryption. Anyone can decode %26 back to &; the goal is unambiguous syntax, not secrecy.
encodeURIComponent(value);encodeURI(value);new URLSearchParams({ q: value }).toString();decodeURIComponent("%E0%A4%A"); // throws URIError- encodeURIComponent
rock%20%26%20roll%3F%20yes%2F%231%20%E2%98%95- encodeURI
rock%20&%20roll?%20yes/#1%20%E2%98%95- URLSearchParams
q=rock+%26+roll%3F+yes%2F%231+%E2%98%95- decodeURIComponent round trip
rock & roll? yes/#1 ☕- decodeURI round trip
rock & roll? yes/#1 ☕- + through URLSearchParams
rock and roll- + through decodeURIComponent
rock+and+roll- malformed %E0%A4%A
URIError
Notice that encodeURI keeps URL punctuation like ?, &, =, /, and #, while encodeURIComponent wraps them. URLSearchParams uses + for spaces in query strings.
| Tool | Best for | Important behavior |
|---|---|---|
encodeURIComponent(value) | One query value, path segment, or hash value | Escapes reserved URL punctuation such as ?, &, =, /, and # as data. |
encodeURI(url) | A mostly complete URL | Leaves reserved punctuation alone: ; , / ? : @ & = + $ #. |
URLSearchParams | A whole query string | Uses form encoding: spaces serialize as +, and values decode correctly through params. |
decodeURI / decodeURIComponent | Undoing the matching encoding | Both throw URIError on malformed percent escapes, and lone surrogates also make URI encoders throw. |
- A single query value: rock & roll
- A whole URL with a space in the path: https://example.com/a b?x=1
- One path segment that literally contains /
- Building a query from
{ q, page } - Filters like
tag=js&tag=web - A hash value typed by a user: intro#part
Sort each task by the helper you would reach for first.
URL.canParse, URL.parse, and safer API URLs
STEP THROUGHURL.canParse(input, base?) answers with a boolean instead of throwing. It is useful for form validation and optional user input. URL.parse(input, base?) is newer: it returns a URL object or null instead of throwing. Both are recent additions to browsers, and URL.parse is the newer one, so feature-detect it before relying on it in code that must run in older browsers.
If URL.parse is missing, wrap new URL() in try/catch and return null on failure. That gives callers a value to branch on instead of an exception to catch.
Now watch the practical bug these helpers prevent. The unsafe line below concatenates a query string by hand. It works for boring words, but breaks as soon as the search text contains an ampersand.
Pick a search term, predict which URL survives special characters, then step through the recorded code.
script
const unsafe = base + "?q=" + "rock & roll";const url = new URL(base);url.searchParams.set("q", "rock & roll");url.searchParams.append("tag", "music & culture");console.log(unsafe);console.log(url.href);The safe version treats the search text as data. set and append encode the values, so the server receives exactly the user’s search and exactly the tag you intended.
Where you’ll use this
URLs appear in routing, sharing links, analytics dashboards, pagination, OAuth return addresses, image CDNs, and every lesson that fetches data from a server. The pattern is almost always the same: parse a trusted base, add path or query pieces with URL-aware APIs, and only then read href.
- Search pages: start from
new URL(location.href), changesearchParams, then update the address bar. - API clients: start from a known base endpoint, append filters and page numbers, and pass
url.hreftofetch. - Redirect validation: use
URL.canParse, then checkoriginbefore trusting a return URL. - Link generation: encode each dynamic path segment with
encodeURIComponentif it might contain/.
Common misconceptions
- “A URL is just a string.” It can be displayed as a string, but the parser knows structured rules that string methods do not.
- “The hash goes to the server.” Browsers keep the fragment locally for navigation inside the page; HTTP requests do not include it.
- “
encodeURIandencodeURIComponentare interchangeable.” One preserves URL punctuation for a whole address; the other protects one piece of data. - “A plus sign always means a space.” It means space in form-encoded query strings, so
URLSearchParamshandles it.decodeURIComponent("+")returns+, not a space. - “Turning params into an object keeps repeated keys.”
Object.fromEntries(new URLSearchParams("tag=js&tag=web"))keeps the lasttag. UsegetAllfor every value.
Practice: URL objects
5 EXERCISESRead the starter code. What does the first console.log print?
const url = new URL("https://example.com:443/docs?q=hi#top");
console.log(url.origin);
console.log(url.port);
console.log(url.hash);The first log prints https://example.com. The URL had :443, but 443 is the default HTTPS port, so the normalized origin omits it.
What does the second console.log print?
const url = new URL("https://example.com:443/docs?q=hi#top");
console.log(url.origin);
console.log(url.port);
console.log(url.hash);The second log prints the empty string because :443 is the default for https:. The third log prints #top.
Run the code or reason it out. What encoded text appears for the tag value?
const url = new URL("https://shop.example/search");
url.searchParams.set("q", "red shoes");
url.searchParams.append("tag", "sale & outlet");
console.log(url.href);The URL contains tag=sale+%26+outlet. URLSearchParams encoded the space as + and the ampersand as %26, so the whole phrase remains one tag value.
A teammate writes "/search?q=" + search. Explain why it breaks for rock & roll, then rewrite it with URL.
const search = "rock & roll";
const url = new URL("https://example.com/search");
url.searchParams.set("q", search);
console.log(url.href);The bug is '/search?q=' + search. The raw & looks like query punctuation. searchParams.set serializes it as q=rock+%26+roll, which keeps the whole search phrase together.
What does Object.fromEntries(params).tag keep?
const params = new URLSearchParams("tag=js&tag=web&page=2");
console.log(params.getAll("tag").join(","));
console.log(Object.fromEntries(params).tag);The first log prints js,web. The second log prints web because Object.fromEntries writes tag twice and the later value replaces the earlier one. Use getAll when repeats matter.
Quiz: check your understanding
7 QUESTIONSAnswer once from your prediction, then read every explanation.
Question 1 of 7What does
new URL('/docs', 'https://example.com/app/')create?Read the code, then predictconsole.log(new URL("/docs", "https://example.com/app/").href);Choose an answer to see the explanation.
Question 2 of 7Which property omits the default HTTPS port 443?
Read the code, then predictconst url = new URL("https://example.com:443/a"); console.log(url.port);Choose an answer to see the explanation.
Question 3 of 7What does
URLSearchParamsuse for spaces when it serializes a query?Read the code, then predictconsole.log(new URLSearchParams({ q: "rock and roll" }).toString());Choose an answer to see the explanation.
Question 4 of 7Which method keeps repeated query values?
Read the code, then predictconst params = new URLSearchParams("tag=js&tag=web"); console.log(params.getAll("tag").join("|"));Choose an answer to see the explanation.
Question 5 of 7Which encoder should protect one path segment named
reports/2026so the slash is data?Choose an answer to see the explanation.
Question 6 of 7What happens when
decodeURIComponent('%E0%A4%A')runs?Choose an answer to see the explanation.
Question 7 of 7What is
URL.canParse('notes', 'https://example.com/app/')?Read the code, then predictconsole.log(URL.canParse("notes", "https://example.com/app/"));Choose an answer to see the explanation.
Key takeaways
new URL(input, base?)parses, resolves, normalizes, and throwsTypeErrorfor invalid URLs.url.searchParamsis live-linked tourl.searchand is the safest way to build query strings.URLSearchParamsserializes spaces as+;encodeURIComponentuses%20.encodeURIis for whole URLs;encodeURIComponentis for one piece of data.URL.canParsereturns a boolean, andURL.parsereturns a URL ornullwhen available.
Final definition: URL objects turn address strings into editable, validated parts, and URLSearchParams turns query strings into safe labeled values.
Up next: Streams & progress.