Web Platform APIs · 3 / 10
lesson 3

URLs and search params

Building query strings without bugs. URL, URLSearchParams, and the encoding traps.

~ 13 min read·lesson 3 of 10
0 / 10

You're adding a filter to a product list. The user picks a category, types a keyword, and your code builds the URL ?category=shoes&q=running. Easy. Then someone searches for M&M's and the page breaks — the & gets eaten by the URL itself and you end up with q=M&M's parsed as two params. Encoding bugs are the longest-running joke in web development, and they all come from one mistake: building URL strings with + and template literals instead of using the tools the browser already gives you.

The URL and URLSearchParams classes are the right shape for this. Once you use them, encoding becomes someone else's problem.

The URL constructor

new URL(string, base?) parses a URL into its parts. The base is what fills in any missing pieces — usually location.origin for "wherever this page lives".

parse.js
const url = new URL("/products?category=shoes", "https://shop.example.com");
console.log(url.pathname);    /* "/products" */
console.log(url.host);        /* "shop.example.com" */
console.log(url.search);      /* "?category=shoes" */

The pieces you can read or write: protocol, host, hostname, port, pathname, search, hash, searchParams. Setting any of them and then printing url.toString() rebuilds a correct URL — you don't have to glue the pieces with strings.

Tip

Pass a relative URL plus a base, and URL resolves it for you the same way the browser does. new URL("../about", "https://x.com/blog/post") gives you https://x.com/about. Useful when joining paths from configuration.

URLSearchParams

url.searchParams is a URLSearchParams object. Its methods are short and obvious — and crucially, it does encoding for you on every operation.

build.js
const url = new URL("https://shop.example.com/search");
url.searchParams.set("q", "M&M's");           /* & gets encoded as %26 */
url.searchParams.set("category", "candy");
console.log(url.toString());
/* https://shop.example.com/search?q=M%26M%27s&category=candy */

Notice what didn't happen: you never typed %26 or %27. You wrote the literal string M&M's and the params object encoded the special characters into a form a URL can carry. That's the whole win.

The methods you'll use most:

  • params.set(name, value) — set or replace.
  • params.get(name) — read one back; returns null if absent.
  • params.append(name, value) — add another value (allowed: ?tag=red&tag=blue).
  • params.delete(name) — remove all values for that name.
  • params.has(name) — check existence.
  • params.toString() — render the whole thing back to a query string.
multi.js
const params = new URLSearchParams();
params.append("tag", "red");
params.append("tag", "blue");
console.log(params.toString());        /* "tag=red&tag=blue" */
console.log(params.getAll("tag"));     /* ["red", "blue"] */

set replaces all existing values for that name. append adds another. The two are easy to mix up — if you mean "and also", use append; if you mean "this is the value", use set.

Building strings the right way

The wrong way (and the one most beginners reach for first):

broken.js
/* Looks fine until a value contains & or = or a space. */
const url = "/api/search?q=" + query + "&category=" + category;

If query is "red&blue", the URL becomes ?q=red&blue&category=shoes — three params, none of them what you wanted. Every search box in the world has shipped this bug at least once.

The right way:

ok.js
const url = new URL("/api/search", location.origin);
url.searchParams.set("q", query);          /* encodes & and = automatically */
url.searchParams.set("category", category);
const response = await fetch(url);         /* fetch accepts URL objects */

fetch accepts a URL directly — you don't have to call .toString(). And because searchParams.set does the encoding, you can hand it any string a user typed without checking it for special characters.

check your understanding
You build a URL by concatenating: "/search?q=" + query. The user types "a + b". What ends up at the server?
Watch out

Don't reach for encodeURIComponent by hand if you can avoid it — URLSearchParams is the higher-level fix. encodeURIComponent is fine when you really do need to encode a single piece of a path or fragment, but for query strings, the params object is shorter and has fewer footguns.

Reading params off a real URL

The reverse operation — reading params from the URL the browser is currently on — uses the same URLSearchParams class, fed by location.search.

read-current.js
const params = new URLSearchParams(location.search);
const query = params.get("q") ?? "";
const tags = params.getAll("tag");

params.get returns null when a param is missing — the ?? "" gives you a sensible default. params.getAll is what you reach for when the same name can repeat (?tag=red&tag=blue).

You can also iterate:

iterate.js
for (const [name, value] of params) {
console.log(name, "=", value);
}
check your understanding
The current URL is ?tag=red&tag=blue&q=ball. You write params.get("tag"). What do you get?

Try it yourself

check your understanding
You want to add page=2 to the current URL while preserving every other param. Which version is right?
check your understanding
You need to send a GET request with a list: tags=["red","blue","green"]. Which is the standard way to encode the list?
check your understanding
What's the difference between params.set("q", "x") and params.append("q", "x")?
← prevnext lesson →
KeepLearningcertificate
for completing
Web Platform APIs
0 of 10 read