Guide

Repeated query string parameters: how to preserve every value

Learn how repeated parameters work, why plain objects lose values, and when to use getAll, append, or an ordered list.

by Tools in a Tab · Published on · Reviewed on

Short answer

A query string may repeat the same key, and every occurrence is a separate entry: tag=astro&tag=seo. Preserve both with an ordered list of pairs or URLSearchParams.getAll("tag"). Converting the query directly to a plain { tag: value } object commonly discards information because one property can hold only one value at a time.

What a repeated key actually means

URL syntax does not require every name to appear once. This query contains three entries even though it has only two distinct keys:

?tag=astro&tag=seo&page=2

The observable order is tag=astro, tag=seo, then page=2. The WHATWG URL Standard models URLSearchParams as a list of pairs, not a dictionary. As a result, get("tag") returns the first value while getAll("tag") returns ['astro', 'seo']. The URLSearchParams API also distinguishes append(), which adds another entry, from set(), which replaces the first value and removes later entries with the same name.

Common ways to represent arrays

There is no universal array convention for query strings. Three APIs may require three different contracts:

tag=astro&tag=seo
tag[]=astro&tag[]=seo
tag=astro,seo

The first form repeats a key. The second includes square brackets literally in the key name. The third contains one comma-bearing value; the comma might be a separator or part of the data itself. Follow the receiving API’s documentation. Automatically converting between these forms can change meaning.

The query string parser displays one row per entry and rebuilds the list without guessing an array convention. Its JSON is an ordered list:

[
  { "key": "tag", "value": "astro" },
  { "key": "tag", "value": "seo" }
]

Spaces, plus signs, and empty values

URLSearchParams uses application/x-www-form-urlencoded. When parsed, + becomes a space and %2B becomes a literal plus. Therefore, q=a+b&q=a%2Bb represents the values a b and a+b.

Empty values should also be preserved. flag and flag= both produce a name flag with an empty string in URLSearchParams; whether an API treats their original spellings differently depends on its own parser. An explicit empty key such as =value is another list entry, although it may indicate a contract mistake.

A safe JavaScript pattern

Iterate over every pair when order and duplicates matter:

const params = new URLSearchParams(location.search);
const entries = [...params.entries()];
const tags = params.getAll('tag');

Use append('tag', 'new') to add another value. Use set() only when you deliberately want one occurrence. Avoid Object.fromEntries(params) when duplicates matter: the resulting object retains only the last value for each name.

Checklist

  1. Confirm how the receiver documents list parameters.
  2. Preserve order and duplicates while parsing.
  3. Distinguish a + space from a %2B literal plus.
  4. Decide explicitly whether empty values are allowed.
  5. Serialize from pairs, not from an object that has already lost data.