completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

URL & URLSearchParams

Learn to parse, build, encode, decode, validate, and safely edit URLs with URL and URLSearchParams instead of fragile string concatenation.

By the end you can
  • 01
    Read every part of a URLUse new URL(input, base) to read the protocol, host, path, query string, and hash.
  • 02
    Build query strings safelyUse url.searchParams methods to add filters, page numbers, and repeated names.
  • 03
    Pick the right toolKnow when to use encodeURI, encodeURIComponent, the decoders, URL.canParse, and URL.parse.

A URL is an address with parts

A URL is the address of a page or file on the web, like https://example.com/docs?q=url. It looks like one piece of text. But it is made of parts:

  • The protocol, like https:, says how to fetch the page.
  • The host, like example.com, names the computer (the server) that has the page.
  • The port, like :8443, is a door number on that computer. Most URLs leave it out.
  • The path, like /docs/url, says which page on that computer.
  • The query string, like ?q=url, carries extra data as name=value pairs.
  • The hash, like #examples, points to one spot inside the page.

JavaScript has two built-in tools for these parts:

  • URL reads a whole address and splits it into its parts.
  • URLSearchParams reads and changes the query string, the part after ?.

Why not just join strings with +? Joining works for plain words. It breaks when the text has special characters. Say a user searches for rock & roll. In a URL, & means "a new pair starts here". So the search gets cut in two. Browsers and Node.js already know all the URL rules. These two tools use those rules for you.

Plain definition

new URL(input, base?) reads an address and splits it into parts you can read and change. The ? after base means you may leave it out. url.searchParams is a URLSearchParams object for the query string. It is linked to the URL: change one, and the other changes too.

Real-life analogyA URL is like an address on an envelope

An address on an envelope is one block of writing. But the post office reads it in parts: the city, the street, the door. A URL works the same way. The punctuation marks, like ://, /, ?, and #, show where one part ends and the next part begins.

In real life: Post or courier
In JavaScript: protocol, like https:
In real life: City and street
In JavaScript: host, like example.com
In real life: Flat door number
In JavaScript: port, like :8443
In real life: Room inside the flat
In JavaScript: path (pathname in code), like /docs/url
In real life: A note with questions
In JavaScript: query string (search in code), like ?q=url
In real life: A sticky tab on one page of a book
In JavaScript: hash, like #examples

If strings are still new to you, keep the Strings and Unicode and strings lessons open. This lesson also turns query pairs into an object. The Object.keys, values and entries lesson explains how that works.

Split an address with new URL()

INTERACTIVE

To parse text means to read it and split it into its parts. new URL() parses an address. You can call it in two ways:

  • new URL(input) needs a full address that starts with a protocol, like https://example.com/docs. A full address is called an absolute URL.
  • new URL(input, base) also takes a short address, like ../search. A short address is called a relative URL. JavaScript works out the full address from the base, the full address you pass second.

If the input is not a valid URL, new URL() stops and throws a TypeError. It does not guess.

The new URL object has one property for each part. It also tidies some parts, so the same address always looks the same:

  • href is the whole address.
  • origin is the protocol, host, and port together, like https://example.com:8443. It answers "which site is this?"
  • port is an empty string, "", when the URL uses the usual port for its protocol. For https:, the usual port is 443.
  • hostname writes a name with non-English letters, like mañana.example, in plain letters, digits, and dashes that start with xn--. This form is called punycode.
  • hash keeps its #. Only the browser uses the hash. It is not sent to the server.
Read each part of one URLPop out in the code editor (opens in a new tab)JavaScript
const url = new URL("https://example.com:8443/docs/page?tab=api#examples");
console.log(url.protocol);
console.log(url.host);
console.log(url.pathname);
console.log(url.search);
console.log(url.hash);
console.log(url.origin);

Line 1 parses one absolute URL. Lines 2 to 7 print six of its parts:

  • Line 2 prints the protocol, https:.
  • Line 3 prints the host, example.com:8443. host includes the port. hostname would leave the port out.
  • Line 4 prints the path, /docs/page.
  • Line 5 prints the query string, ?tab=api. It keeps its ?.
  • Line 6 prints the hash, #examples.
  • Line 7 prints the origin, https://example.com:8443.
The URL https://example.com:8443/docs/page?tab=api#examples split into five cards: protocol https:, host example.com:8443, pathname /docs/page, search ?tab=api, and hash #examples.https:example.com:8443/docs/page?tab=api#examplesprotocolhostpathnamesearchhash
new URL() splits one address into named parts. Each card is the value of one property, such as url.host.
Go up one folder from a base addressPop out in the code editor (opens in a new tab)JavaScript
const base = "https://example.com/docs/guides/";
const url = new URL("../search?q=rock", base);
console.log(url.href);

The base ends in /guides/. The input ../search means "go up one folder, then find search". So JavaScript leaves guides/ and stays in /docs/. Line 3 prints https://example.com/docs/search?q=rock.

An input that starts with / works differently. It starts from the top of the site. So /search with the same base gives https://example.com/search.

See the parts of a URL
The code behind this tablePop out in the code editor (opens in a new tab)JavaScript
const url = new URL(input, base);console.log(url.href);console.log(url.protocol);console.log(url.hostname);console.log(url.searchParams.getAll("tag"));
The URL's partsvalid
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)
Try it yourself

This is a valid URL. URL.canParse gives true. Checking for URL.parse in your browser….

Try each preset: a username and password, an IPv6 address, a non-English domain, repeated names, a hash, and a relative URL with a base. The table shows the parts the URL object stores.

Try the "Unicode domain" preset. The hostname row shows the punycode form. In href, the letters é and í in the path and hash turn into codes like %C3%A9. This is called percent-encoding; a later section explains it.

Then try the "Username + password" preset. A URL can hold a username and a password before the @. Real apps should not show them or write them to logs, because anyone who sees the URL would see the password.

Build a query string with URLSearchParams

INTERACTIVE

The query string is the part after ?. It is a list of name=value pairs, joined by &. For example, ?q=boots&page=2 has two pairs: q is boots, and page is 2.

The same name can appear more than once. Shops often do this for filters: ?tag=js&tag=web.

Real-life analogyURLSearchParams is like a paper form

On a paper form, you don't write & and = marks yourself. You just fill in labeled boxes. URLSearchParams works like that form. You give it names and values. It writes the query string for you, and it writes special characters in a safe form.

In real life: Writing in the box labeled Search
In JavaScript: set('q', value)
In real life: Ticking several topics
In JavaScript: append('tag', value), called more than once
In real life: Rubbing out a box
In JavaScript: delete('page')
In real life: Putting the boxes in A to Z order
In JavaScript: sort()

Every URL object has a searchParams property. It is linked to the URL. When you change searchParams, url.search and url.href change right away. These are its main methods:

  • set(name, value) puts in one value. It replaces any old values with that name.
  • append(name, value) adds one more value. Old values with that name stay.
  • delete(name) removes every pair with that name.
  • has(name) returns true if a pair with that name is there.
  • get(name) returns the first value for that name, or null if there is none.
  • getAll(name) returns every value for that name, as an array.
  • sort() puts the pairs in order by name.
  • toString() writes the query string, without the ?.

You can also loop over it with for...of. Each step of the loop gives one [name, value] pair.

Change and read a query with repeated namesPop out in the code editor (opens in a new tab)JavaScript
const params = new URLSearchParams("tag=js&tag=web&page=2");
params.set("page", "3");
params.append("tag", "url");
console.log(params.get("tag"));
console.log(params.getAll("tag"));
console.log(params.has("page"));
params.delete("page");
params.sort();
console.log(params.toString());
  • Line 1 makes params from a query string. It has two tag values, js and web, and a page, 2.
  • Line 2 uses set to replace page 2 with 3.
  • Line 3 uses append to add a third tag, url. The two old tags stay.
  • Line 4 uses get, so it prints only the first tag: js.
  • Line 5 uses getAll, so it prints all three: [ 'js', 'web', 'url' ].
  • Line 6 prints true, because page is still there.
  • Line 7 removes page. Line 8 sorts the pairs by name. Only tag pairs are left, and sort keeps pairs with the same name in their old order.
  • Line 9 prints tag=js&tag=web&tag=url.
After line 3, the params cart holds four pairs: tag js, tag web, page 3, and tag url. get of tag gives only js. getAll of tag gives an array of js, web, and url.page: 3tag: urltag: jstag: webparamsget("tag")"js"getAll("tag")["js","web","url"]
After line 3, params holds three tag pairs and one page pair. get gives only the first tag; getAll gives every tag.
Build a query string
The code that builds this queryPop out in the code editor (opens in a new tab)JavaScript
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]);
The query nowspaces become +
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
pairs from a loop
[["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"}
Try it yourself

The URL now: https://example.com/search?q=rock+%26+roll+%F0%9F%8E%B8&tag=music&tag=live+shows&page=2

searchParams is linked to the URL. Change a field, and url.href changes right away.

Look at the spaces in the lab's URL. URLSearchParams writes each space as +. Web forms have always sent spaces this way, so query strings follow the same rule. The rule has a long name: application/x-www-form-urlencoded. When you read a value back through URLSearchParams, each + turns back into a space.

Encode special characters, then decode them

INTERACTIVE

Some characters have a job in a URL:

  • & separates one pair from the next.
  • = separates a name from its value.
  • ? starts the query string.
  • # starts the hash.
  • / separates the folders in the path.

Sometimes these characters are part of the data, like the & in rock & roll. Then they must be written in a safe form, so they are not read as URL punctuation.

That safe form is called percent-encoding. Each special character becomes % and a code. For example, & becomes %26, and a space becomes %20. Some characters, like emoji, need several codes. To encode text means to write it in this safe form. To decode it means to turn it back into normal text.

Real-life analogyEncoding wraps fragile things in a parcel

A shipping clerk reads every label on a parcel. If a loose item inside has writing on it, the clerk may read that writing as part of the address. So you wrap the item. Encoding wraps a special character as a percent code, such as %26, so nobody reads it as punctuation.

In real life: Bubble-wrapping a glass
In JavaScript: percent-encoding a space, &, =, ?, #, or emoji
In real life: Leaving the address label unwrapped
In JavaScript: encodeURI leaves URL punctuation as it is
In real life: Wrapping one item all over
In JavaScript: encodeURIComponent encodes every special character in one value
Compare the encoders
Three encoders and one broken decodePop out in the code editor (opens in a new tab)JavaScript
encodeURIComponent(value);encodeURI(value);new URLSearchParams({ q: value }).toString();decodeURIComponent("%E0%A4%A"); // throws URIError
Resultsmade by your browser
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, back again
rock & roll? yes/#1 ☕
decodeURI, back again
rock & roll? yes/#1 ☕
+ read by URLSearchParams
rock and roll
+ read by decodeURIComponent
rock+and+roll
broken %E0%A4%A
URIError
Try it yourself

encodeURI leaves URL punctuation like ?, &, =, /, and # as it is. encodeURIComponent encodes them. URLSearchParams writes each space as +.

The last row always decodes the same broken text, %E0%A4%A, so you can see the URIError.

The table lists the tools. Each row says what one tool is for and what it does.

Which tool encodes or decodes what
ToolUse it forWhat it does
encodeURIComponent(value)One piece of data: one query value, one folder name in a path, or one hash value.It encodes URL punctuation such as ?, &, =, /, and #, so they count as data.
encodeURI(url)A whole URL that already has its punctuation.It leaves URL punctuation as it is: ; , / ? : @ & = + $ #. It encodes spaces and other characters.
URLSearchParamsA whole query string.It writes each space as + and encodes the rest. Reading a value back through it decodes the value.
decodeURI / decodeURIComponentTurning encoded text back into normal text.Both throw a URIError if a percent code is broken, like %E0%A4%A. The encoders throw URIError too, if the text has half of an emoji on its own.

Here is the difference on one folder name, reports/2026. The slash is part of the name, not a split between two folders.

The folder name reports/2026 goes into two encoders. encodeURI gives back reports/2026, so the slash still splits the path. encodeURIComponent gives reports%2F2026, so the slash is just data.reports/2026encodeURIreports/2026/ still splitsencodeURIComponentreports%2F2026/ is just data
For one folder name, encodeURI leaves the slash alone, so it still splits the path. encodeURIComponent turns it into %2F, so the name stays in one piece.
Which encoder?
  • One query value: rock & roll
  • A whole URL with a space in the path: https://example.com/a b?x=1
  • One folder name in a path that contains /
  • Building a query from { q, page }
  • Filters like tag=js&tag=web
  • A hash value typed by a user: intro#part
Try it yourself
0 of 6 correct

Sort each task by the tool you would use first.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Check a URL without errors, then build API URLs safely

STEP THROUGH

new URL() throws an error for a bad address. Sometimes you just want a yes or no answer. For example, a form may want to check what a user typed. Two tools help. They are static methods: you call them on URL itself, not on a URL object.

  • URL.canParse(input, base?) returns true or false. It never throws.
  • URL.parse(input, base?) returns a URL object, or null if the input is not valid. It never throws either.

Both tools are new. URL.parse is the newest, so some older browsers don't have it yet. Check that it exists before you use it, for example with typeof URL.parse === "function". This check is called feature detection.

Your own parse-or-null helper

If URL.parse is missing, write a small helper. Inside it, call new URL() inside try/catch. If it throws, return null. Now your code can check if (url === null) instead of catching an error.

Next is the bug these tools help you avoid. An API here is an address your code calls to get data from a server. The code below builds an API address in two ways:

  • Line 2 joins the search text onto the address with +.
  • Lines 3 to 5 use URL and searchParams.

Joining works for a plain word. It breaks when the search text has an &.

Joining strings vs using searchParams
Step 0 of 7Ready
Your turn: follow the blue line

Pick a search term. Guess which URL survives special characters. Then step through the code.

Running in
  1. script
Next: line 1
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);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Search term on line 2 and line 4

Picking a term starts a new run. Try rock & roll first.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

The safe version treats the search text as data. set and append encode each value. So the server gets exactly what the user typed, and exactly the tag you added.

Left: joining ?q= and rock & roll by hand. The server reads two pairs, q is rock with a space, and an empty pair named space roll. Right: set q to rock & roll gives ?q=rock+%26+roll, which the server reads as one value, rock & roll."?q=" + "rock & roll"?q=rock & rollq: "rock "" roll": ""split in twoset("q", "rock & roll")?q=rock+%26+rollq: "rock & roll"one value
Joined by hand, the & starts a new pair and cuts the search in two. set encodes it as %26, so the server gets one value.

Where you’ll use this

Your code will meet URLs in many places: menu links, share buttons, page numbers, sign-in pages that send you back where you were, and every lesson that fetches data from a server. The steps are almost always the same:

  1. Start from a base address you trust.
  2. Add path or query parts with the URL tools, not with +.
  3. Read href at the end.

Some places you'll do this:

  • Search pages: start from new URL(location.href). location.href is the address of the current page. Change searchParams, then show the new address in the address bar.
  • API calls: start from the API's base address. Append filters and page numbers. Then pass url.href to fetch, the function that asks a server for data.
  • Return addresses: a sign-in page often gets a "go back to" URL. Check it with URL.canParse. Then compare its origin with your own site before you send the user there.
  • Links you build: encode each changing folder name with encodeURIComponent if it might contain /.

Marketing links often carry labels in the query string. utm_source says where the click came from. utm_campaign names the campaign.

Read campaign labels from a linkPop out in the code editor (opens in a new tab)JavaScript
const url = new URL("https://example.com/news?utm_source=email&utm_campaign=fall");
console.log(url.searchParams.get("utm_source"));
console.log(url.searchParams.get("utm_campaign"));

Line 1 parses the full link once. Line 2 reads utm_source and prints email. Line 3 reads utm_campaign and prints fall. If a name is missing, get returns null. That is easier than cutting the raw text at each &.

Accept only https links from usersPop out in the code editor (opens in a new tab)JavaScript
function safeLink(input, base) {
  if (!URL.canParse(input, base)) return null;
  const url = new URL(input, base);
  return url.protocol === "https:" ? url.href : null;
}
console.log(safeLink("https://example.com/help", "https://app.example.com/"));
console.log(safeLink("javascript:alert(1)", "https://app.example.com/"));
  • Line 2 returns null if URL.canParse says the text is not a URL at all.
  • Line 4 returns the link only if its protocol is https:. Otherwise it returns null.
  • Line 6 passes an https link, so it prints https://example.com/help.
  • Line 7 passes javascript:alert(1). That is a valid URL, but clicking it runs code. Its protocol is javascript:, so line 4 returns null.

For a real return address, also compare origin with a list of sites you allow. Checking only that the text starts with https:// doesn't tell you which site the user will land on.

Common misconceptions

  • “A URL is just a string.” You can show a URL as a string. But it has parts and rules. new URL() knows these rules. String methods like split don't.
  • “The hash goes to the server.” The browser keeps the hash. It uses it to jump to a spot in the page. Requests to the server don't include it.
  • “encodeURI and encodeURIComponent do the same thing.” encodeURI is for a whole address, so it leaves punctuation like / and ? alone. encodeURIComponent is for one piece of data, so it encodes them.
  • “A plus sign always means a space.” Only in query strings, which follow the web form rule. URLSearchParams uses that rule. decodeURIComponent("+") returns +, not a space.
  • “Turning params into an object keeps repeated names.” Object.fromEntries(new URLSearchParams("tag=js&tag=web")) keeps only the last tag, web. An object can't have two properties with the same name. Use getAll to get every value.

Practice: URL objects

5 EXERCISES
Exercise 1 · Warm-upPredict the origin

Read the starter code. What does the first console.log print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
const url = new URL("https://example.com:443/docs?q=hi#top");
console.log(url.origin);
console.log(url.port);
console.log(url.hash);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeSpot the empty port

    What does the second console.log print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const url = new URL("https://example.com:443/docs?q=hi#top");
    console.log(url.origin);
    console.log(url.port);
    console.log(url.hash);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeBuild a safe search URL

      Run the code or work it out. What does the tag part of the URL look like?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const url = new URL("https://shop.example/search");
      url.searchParams.set("q", "red shoes");
      url.searchParams.append("tag", "sale & outlet");
      console.log(url.href);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeFind the bug in a shared link

        A teammate writes "/search?q=" + search. Explain why it breaks for rock & roll. Then rewrite it with URL.

          Exercise 5 · ChallengeRepeated names in an object

          What does Object.fromEntries(params).tag keep?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const params = new URLSearchParams("tag=js&tag=web&page=2");
          console.log(params.getAll("tag").join(","));
          console.log(Object.fromEntries(params).tag);

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS

            Pick your answer before you check it. Then read every explanation.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What does new URL('/docs', 'https://example.com/app/') create?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(new URL("/docs", "https://example.com/app/").href);

              Choose an answer to see the explanation.

            2. Question 2 of 7What does url.port print when the URL uses 443, the usual HTTPS port?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const url = new URL("https://example.com:443/a");
              console.log(url.port);

              Choose an answer to see the explanation.

            3. Question 3 of 7How does URLSearchParams write spaces when it turns a query into text?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(new URLSearchParams({ q: "rock and roll" }).toString());

              Choose an answer to see the explanation.

            4. Question 4 of 7Which call returns every value for a repeated name?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const params = new URLSearchParams("tag=js&tag=web");
              console.log(params.getAll("tag").join("|"));

              Choose an answer to see the explanation.

            5. Question 5 of 7One folder name in a path is reports/2026. Which encoder makes its slash count as data?

              Choose an answer to see the explanation.

            6. Question 6 of 7What happens when decodeURIComponent('%E0%A4%A') runs?

              Choose an answer to see the explanation.

            7. Question 7 of 7What is URL.canParse('notes', 'https://example.com/app/')?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(URL.canParse("notes", "https://example.com/app/"));

              Choose an answer to see the explanation.

            Key takeaways

            • new URL(input, base?) splits an address into parts. It works out a relative address from the base, tidies the parts, and throws a TypeError for an invalid URL.
            • url.searchParams is linked to url.search. It is the safest way to build a query string.
            • URLSearchParams writes a space as +. encodeURIComponent writes it as %20.
            • encodeURI is for a whole URL. encodeURIComponent is for one piece of data.
            • URL.canParse returns true or false. URL.parse returns a URL or null, in browsers that have it.

            Final definition: A URL object splits an address into parts you can read and change safely. URLSearchParams turns the query string into named values and encodes them for you.

            Up next: Streams & progress.

            CompleteFrontend Clear concepts. Working examples.