Topic 11.4
URL, URLSearchParams and location
In one line
The URL class splits any web address into reliable parts and resolves relative links, URLSearchParams reads and builds query strings with correct encoding, and location is the current page's URL, which you can read and change to navigate.
Think of it like this
A postal address on an envelope. It has a fixed order: country, city, street, house number, and maybe "attention: Asha" at the bottom. If you cut the address up by hand with scissors, you'll eventually cut in the wrong place (what if the street name contains a comma?). The URL class is a post-office machine that reads any address and hands you each part, cut correctly every time.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- URL
- A web address, like
https://hectal.in/javascript?tab=1#top. - Hostname and host
- The site's name (
hectal.in); the host is the hostname plus the port if one is written. - Pathname
- The part after the host that says which page or file:
/javascript/roadmap. - Query string
- The part after
?:name=valuepairs joined by&. Also called the search. - Hash (fragment)
- The part after
#. It points inside the page and is never sent to the server. - Relative URL
- A partial address like
../aboutthat only makes sense next to a base URL. - Percent-encoding
- Writing special characters as
%plus a code, like a space as%20, so they travel safely inside a URL. - location
- The browser object for the current page's URL. Changing it can load a new page.
Step by step
01Name the parts
Every part has a property with the same name on URL objects and on location.
02Parse instead of splitting strings
url.split("?")[1].split("&") works until a value contains &, = or #, or the URL has no query at all. The parser handles every case.
const url = new URL("https://shop.example.com/search?q=tea%20%26%20coffee&page=2");
console.log(url.searchParams.get("q")); // "tea & coffee"
console.log(Number(url.searchParams.get("page"))); // 203Build a URL safely
Start from a base, then set parameters. Encoding is automatic, and user input can't sneak in an extra &admin=1.
const url = new URL("/api/search", "https://api.example.com");
url.searchParams.set("q", userInput);
url.searchParams.set("limit", "20");
const res = await fetch(url); // fetch accepts URL objects04Read the current page's query
On a real page with the address https://site.com/products?sort=price&tag=new, read the filters on load and fill in the form, so a shared link reopens the same view.
const params = new URLSearchParams(location.search);
const sort = params.get("sort") ?? "popular";
const tags = params.getAll("tag");05Navigate with location
assign and setting href add a history entry; replace doesn't, which suits redirects (after login you don't want Back to return to the login form).
location.assign("/dashboard"); // new page, Back returns here
location.replace("/dashboard"); // new page, Back skips this one
location.hash = "#comments"; // same page: scroll + hashchange, no reloadTry it yourself
- 1
Parse your own address
In the first example, replace the URL with
https://hectal.in/javascript/topic/phase-11/url-apis?ref=home#tryit. Predictpathname,searchandhashbefore running. - 2
Repeat a key
In the second example, tick both sort options by turning the
<select>into<select name="sort" multiple>and selecting both. Predict the link: onesortor two? - 3
Find the default port
Predict, then log
new URL("https://a.com:443/x").portandnew URL("http://a.com:443/x").port. Why is one of them empty?
Code & diagrams
`+` in a query means a space, so `get("q")` gives `red shoes`. A path alone isn't a full URL: give a base as the second argument.
Preview
Console
Nothing logged yet.Expected console output
protocol https:
hostname shop.example.com
port 8080
host shop.example.com:8080
origin https://shop.example.com:8080
pathname /search
search ?q=red+shoes&page=2
hash #reviews
q is red shoes
https://shop.example.com:8080/search?q=red+shoes&page=3
https://a.com/shop/img/a.png
https://a.com/list?page=2
false
TypeError: Failed to construct 'URL': Invalid URL`Object.fromEntries` keeps only the **last** `tag`, so use `getAll` for repeated keys. In the unencoded URL, the server would read `q` as `tea ` and see a second, empty parameter called ` coffee`.
Preview
Console
Nothing logged yet.Expected console output
q=tea+%26+coffee&sort=price&tag=hot&tag=new
tea & coffee [ 'hot', 'new' ] null 4
{ q: 'tea & coffee', sort: 'price', tag: 'new' }
q=tea+%26+coffee&sort=price&page=2
/search?q=tea & coffee
/search?q=tea%20%26%20coffee
https://shop.example.com/search?q=tea+%26+coffee&sort=ratingHectal runs previews in a sandboxed `srcdoc` frame, so `location.href` is `about:srcdoc`. Changing the hash updates `location` at once, but `hashchange` fires a moment later, as a task. Click the links to fire it again.
Preview
Console
Nothing logged yet.Expected console output
href: about:srcdoc
protocol: about:
right after setting: #details
hashchange -> #detailsBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Build a URL with string gluing
In a search box, build the request as fetch("/api/search?q=" + input.value) and type R&D #1.
Break #2
Parse a relative path without a base
Write new URL("/products?id=3").
Myth vs fact
Myth
params.get("page") returns a number when the URL says page=2.
Fact
Everything in a query string is text. You get "2"; convert with Number() and handle null when it's missing.
Myth
The #hash is sent to the server.
Fact
Browsers strip the fragment before sending the request. That's why old single-page apps used #/routes: the server always sees the same URL.
Myth
encodeURI is the right way to encode a query value.
Fact
encodeURI leaves &, =, ? and # alone because it expects a whole URL. For a single value, use encodeURIComponent or URLSearchParams.
Myth
Changing location.search from JavaScript only updates the address bar.
Fact
Setting location.search (or href, pathname) loads a new page. To change the address without reloading, use the History API (next topic).
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
The
URLclass follows the WHATWG URL Standard, the same parser the browser uses for links andfetch, so it agrees with the browser on odd inputs (backslashes, tabs, extra slashes). Hand-written regexes don't, which has led to real security bugs such as open-redirect checks that a parser disagrees with. - ▸
When checking where a redirect goes, compare
new URL(target, location.origin).originto your own origin. Checking that a string "starts with /" isn't enough://evil.exampleis a protocol-relative URL to another site. - ▸
URLSearchParamsuses theapplication/x-www-form-urlencodedrules: spaces become+and*-._stay as they are.encodeURIComponentleaves!'()*unescaped. Servers decode both correctly, but signing a URL (for example, cloud storage signatures) requires the exact encoding the signer expects. - ▸
Text fragments (
#:~:text=some%20words) let links highlight a phrase in a page; they're handled by the browser and aren't visible inlocation.hashin Chromium.
Remember this
- 1
A URL (Uniform Resource Locator) is a web address. Take
https://shop.example.com:8080/search?q=red+shoes&page=2#reviews. The protocol (or scheme) ishttps:. The hostname isshop.example.com, and with the port (8080) it makes the hostshop.example.com:8080. The origin is protocol + host:https://shop.example.com:8080(Topic 11.1). The pathname is/search. The search (or query string) is?q=red+shoes&page=2:name=valuepairs joined by&. The hash (or fragment) is#reviews: a place inside the page, which the browser never sends to the server. - 2
new URL(text)parses an address into an object with all those parts as properties:protocol,host,hostname,port,pathname,search,hash,originand the fullhref. They are settable: changeurl.pathnameorurl.hashandhrefupdates. If the text isn't a valid absolute URL, the constructor throws aTypeError(Failed to construct 'URL': Invalid URL).URL.canParse(text)checks first without throwing, andURL.parse(text)returnsnullinstead of throwing (both newer additions, in all current browsers). - 3
new URL(relative, base)resolves a relative link the same way the browser does for<a href>:new URL("../img/a.png", "https://a.com/shop/items/1")giveshttps://a.com/shop/img/a.png."?page=2"keeps the path and replaces the query;"#top"keeps everything but the hash;"//cdn.com/a.js"keeps only the protocol. The parser also normalises: it lowercases the hostname, removes the default port (:443for https), resolves./and../, and turns international domain names into their ASCII form (münchen.debecomesxn--mnchen-3ya.de). - 4
URLSearchParamshandles the query string.url.searchParamsis a live one attached to a URL (changes update the URL), or you can make one withnew URLSearchParams("a=1&b=2"), from an object, or from a form'sFormData. Methods:get(name)(the first value, ornull),getAll(name)(every value, for repeated keys liketag=a&tag=b),has,set(replace),append(add another),delete,sort,size, and iteration withfor...of.toString()gives the query without the?. Every value comes back as a string:Number(params.get("page")). - 5
Encoding turns characters that have a meaning in URLs (
&,=,?,#,/, spaces, non-English letters) into%codes so they arrive as plain data.URLSearchParamsdoes it for you, writing a space as+(the form encoding).encodeURIComponent(value)encodes one piece, writing a space as%20.encodeURIencodes a whole URL but leaves&,?and/alone, so it's wrong for a single value. Building URLs by gluing strings ("?q=" + term) breaks the moment the term contains&or#, and can let user input change other parameters. - 6
location(alsowindow.location) is the URL of the current page, with the same properties as aURLobject. Reading it is how you get the current query string:new URLSearchParams(location.search). Writing it navigates:location.href = "/login"orlocation.assign("/login")loads a new page and adds a history entry,location.replace("/login")loads it without a history entry (the Back button skips it), andlocation.reload()reloads. Changing onlylocation.hashscrolls to the target and fireshashchangewithout reloading. In Hectal's sandboxed preview,location.hrefisabout:srcdoc, so examples parse fixed URLs instead.
Explain it without notes
Name the parts of a URL and what each is used for.
Why use URL/URLSearchParams instead of string concatenation and split?
What is the difference between location.assign, location.replace and setting location.hash?
How does new URL(relative, base) resolve ../a, ?x=1, #y and //host/z?
Practice
Write getPage(urlText) that returns the page query parameter as a number, or 1 when it's missing or not a positive whole number.
Write withParams(base, obj) that returns a URL string with every key of obj added as a query parameter, skipping null and undefined values.
Trade-offs
- ↔
Keeping UI state (filters, tabs, page number) in the query string makes views shareable and bookmarkable and survives reloads, but every change needs URL updates and very large state makes ugly, long URLs; keep only what a shared link should restore.
- ↔
location.replacekeeps history tidy for redirects but surprises users if used for normal navigation, because Back no longer goes where they expect.
Done when you can
I can name every part of a URL and read it from a
URLobject.I can resolve relative URLs with a base.
I build and read query strings with
URLSearchParams, including repeated keys.I know when to use
encodeURIComponentand why string gluing is dangerous.I can navigate with
location.assign,replaceandhash, and know which add history entries.