Keep repeated query parameters when editing a URL

URLSearchParams supports repeated keys, but set() and Object.fromEntries() can quietly discard them. Use getAll() and append() when a filter accepts multiple values.

A filter URL might look like this:

/products?tag=javascript&tag=testing&sort=newest

The two `tag` entries are intentional: the user selected two filters. Trouble starts when code treats the query string like an object with one value per key.

const url = new URL(
  'https://example.com/products?tag=javascript&tag=testing&sort=newest'
);

console.log(url.searchParams.get('tag'));
// javascript

console.log(Object.fromEntries(url.searchParams));
// { tag: 'testing', sort: 'newest' }

Those operations lose information in different ways. `get()` returns the first matching value. Building an object keeps the last value for a repeated key because an object property can hold only one value at a time.

The useful shift is to treat query parameters as a list of name/value pairs. Some names can appear more than once.

Replace one filter without rebuilding the whole query

Use `getAll()` to read every value. To replace a selection, delete that key’s existing entries and append the new values:

function replaceTags(input, tags) {
  const url = new URL(input);

  url.searchParams.delete('tag');

  for (const tag of tags) {
    url.searchParams.append('tag', tag);
  }

  return url;
}

const next = replaceTags(
  'https://example.com/products?tag=javascript&tag=testing&sort=newest',
  ['accessibility', 'react']
);

console.log(next.searchParams.getAll('tag'));
// [ 'accessibility', 'react' ]

console.log(next.searchParams.get('sort'));
// newest

console.log(next.href);
// https://example.com/products?sort=newest&tag=accessibility&tag=react

Run this in a browser console or Node.js. Then try passing `[]`: both tag entries disappear, while `sort` stays intact.

The gotcha is `set()`. Calling `set('tag', 'react')` replaces the first matching entry and removes the other entries with that name. That’s documented behavior, and it’s useful for a single-value parameter such as `sort`. It won’t preserve a multi-select filter.

Repeated keys are my preference here because `URLSearchParams` handles them directly. Your API still needs to define what they mean: whether tags use AND or OR matching, whether duplicates matter, and whether order matters. The URL API preserves the values; it doesn't decide your filtering rules.

One tradeoff: editing through `searchParams` can normalize encoding and change the serialized query string. Use this approach for ordinary application URLs. If a URL’s signature depends on its exact query text, follow that signing scheme’s rules before modifying it.

Back to Journal