URLSearchParams is the standard JavaScript class that parses and builds a URL query string as an ordered list of name and value pairs, so you never split on ampersands by hand. It is defined by the WHATWG URL Standard and exists in browsers, workers and Node.js. A leading question mark in the input is ignored, and the same name may appear more than once.
You create one from a string, an object, or an array of pairs. Internally it is a list, not a map, so duplicates and order are kept. Both strings and numbers are accepted as values, but everything is converted to a string on the way in.
Decoding turns + into a space and decodes percent sequences as UTF-8. Encoding writes a space as +. That differs from encodeURIComponent, which writes %20.
const p = new URLSearchParams('?tag=a&tag=b&q=hello+world&e=&flag');
console.log(p.get('tag'), p.getAll('tag'), p.get('q'), JSON.stringify(p.get('flag')), p.get('missing'));
p.append('tag', 'c'); p.set('q', 'a b'); console.log(p.toString());
console.log(new URLSearchParams({ a: 1, b: undefined, c: null, d: [1, 2] }).toString());
console.log(new URLSearchParams({ s: "a b~!*'()-._é" }).toString());
const s = new URLSearchParams('b=2&a=1&B=3&a=0'); s.sort(); console.log(s.toString());
console.log(Object.fromEntries(new URLSearchParams('a=1&a=2&b=3')));
Output from Node.js 22.22.0:
a [ 'a', 'b' ] hello world "" null
tag=a&tag=b&q=a+b&e=&flag=&tag=c
a=1&b=undefined&c=null&d=1%2C2
s=a+b%7E%21*%27%28%29-._%C3%A9
B=3&a=1&a=0&b=2
{ a: '2', b: '3' }
get returns only the first value for a name, while getAll returns an array of all values. For ?tag=a&tag=b, get('tag') is "a" and getAll('tag') is ["a", "b"]. A missing name gives null from get and an empty array from getAll. A bare name such as flag gives an empty string, not null, so test with has when you need presence.
It writes + for a space and reads + back as a space. This is the form-encoding rule, and servers that follow the same rule decode it correctly. If an API wants %20, build the string yourself with encodeURIComponent, or replace + after calling toString. A literal plus sign in a value is written as %2B, so 1+2 survives a round trip.