Someone pastes a link in Slack and it’s 600 characters long: utm_source, utm_campaign, a session ID, three filters and something called fbclid. Somewhere in there is the one parameter you need. That tail end of the URL is the query string, and it’s much less scary once you know how it’s put together.
What is a query string
A query string is everything in a URL after the ?. It holds key-value pairs joined with = and separated by &. It’s how a URL passes data to a server: search terms, page numbers, filters, tracking codes, API options.
https://shop.example.com/search?q=hiking+boots&page=2&sort=priceThis one has three parameters: q is “hiking boots”, page is “2” and sort is “price”. The + stands for a space. More on that shortly.
URL encoding explained
Imagine searching for “salt & pepper”. If the & went into the URL as it is, the server would think a new parameter had started. So characters with a special job in URLs, such as &, = and #, plus spaces and non-ASCII characters, get percent-encoded: replaced by % and two hexadecimal digits for each byte of the character in UTF-8.
Space %20 (or + in form encoding)
& %26
= %3D
? %3F
# %23
/ %2F
@ %40
é %C3%A9Parsing means decoding these back to the original characters. Building means encoding every value so nothing in it is mistaken for a separator. Strictly speaking, ?, / and @ are allowed unencoded inside a query string, but encodeURIComponent() encodes them anyway, and that’s always safe.
Building query strings
You can glue a query string together with + and template literals, and it’ll work right up until a user searches for “salt & pepper”. The URLSearchParams API does the encoding for you:
const params = new URLSearchParams();
params.set("q", "salt & pepper grinder");
params.set("page", "1");
params.set("category", "kitchen & dining");
console.log(params.toString());
// q=salt+%26+pepper+grinder&page=1&category=kitchen+%26+diningEach & inside a value became %26, so it can’t be read as a separator. Use set() to replace a value and append() to add another value under the same key.
Parsing query strings with URLSearchParams
The same API reads query strings too. Give it a query string, with or without the leading ?, or use the searchParams property of a URL object. Then ask for values with get(), getAll(), has() and entries():
const url = new URL(
"https://shop.example.com/search?q=hiking+boots&page=2&size=9&size=10",
);
const params = url.searchParams;
console.log(params.get("q")); // "hiking boots"
console.log(params.get("page")); // "2"
console.log(params.getAll("size")); // ["9", "10"]
console.log(params.has("sort")); // false
for (const [key, value] of params.entries()) {
console.log(key, "=", value);
}Notice that size appears twice. That’s allowed, and it’s how multi-select filters and checkbox groups usually send their choices. get() returns only the first value, while getAll() returns them all. Every value comes back as a string, so "2" is not 2 until you convert it.
Common pitfalls
Spaces: plus or %20
Spaces get two spellings. In form encoding (application/x-www-form-urlencoded, used by HTML forms and URLSearchParams), a space is +. In general percent-encoding it’s %20. Both show up in real query strings, and URLSearchParams decodes both.
The trap is decodeURIComponent(). It leaves + alone, so “hiking+boots” stays “hiking+boots” and you spend twenty minutes wondering why your search results are empty.
Hash fragments
The fragment is everything after #. It isn’t part of the query string, and the browser never sends it to the server. If a URL has both, parse them separately. A URL object already splits them into its search and hash properties.
Nested objects and arrays
Query strings are flat: just keys and values. Some back ends, such as PHP and Node’s qs library, read bracket notation like filters[color]=red&filters[size]=large as nested objects. That’s a convention, not part of any URL standard. URLSearchParams sees filters[color] as an ordinary key, so you’ll need a library such as qs to get the nested structure.
How to use the query string parser
The query string parser has two modes, Parse and Build, and runs entirely in your browser.
Parse
- Paste a full URL or just the query part, such as
page=1&sort=name, and press Parse or Enter. Anything after a#is ignored. - Read the decoded parameters in the table. A repeated key gets a row for each value.
- Copy the JSON output, an object of keys and values, if you want it in code. If a key appears more than once, the JSON keeps only its last value.
Build
- Type a key and value on each row. Add parameter adds a row, and the remove button next to a row deletes it.
- Select Build to get a properly encoded query string, without the leading
?, and copy it.
It’s handy for debugging API calls, decoding that 600-character Slack link, or building a query string without encoding it by hand.
