Duplicate Query Parameters: Define One API Policy

Handle duplicate URL query parameters consistently across proxies, signatures and application code, with explicit rules for repeated filters and scalar fields.

In this article

An API must decide what repeated query parameters mean. Some parsers return the first value, some the last and some an array. If a proxy, signature verifier and application disagree, the same URL can be interpreted differently at each layer. Define a policy for each parameter and reject ambiguous scalar input.

Repeated values are not inherently wrong. A search endpoint may intentionally support multiple tags. The problem is allowing repetition without a contract, especially for fields that affect permissions, routing or billing.

Inspect all values before selecting one

javascript
const query = new URLSearchParams('tag=security&tag=ai&limit=10&limit=50');
console.log(query.getAll('tag'));   // ['security', 'ai']
console.log(query.getAll('limit')); // ['10', '50']

The URLSearchParams getAll documentation describes retrieving every value for a name. Use that capability before validating a field that is supposed to occur only once.

Do not flatten parameters into an object first and then claim duplicates were absent. The flattening operation may already have discarded evidence. Keep a list representation until repetition rules have been checked.

Classify parameters by intended shape

For a scalar such as limit, require exactly one value if it is supplied. For a repeated filter such as tag, accept a documented list with a maximum count. For unsupported parameters, decide whether to reject or ignore them consistently.

javascript
function readScalar(params, name) {
  const values = params.getAll(name);
  if (values.length > 1) throw new Error('duplicate scalar parameter');
  return values[0] ?? null;
}

This helper is only one structural check. Validate the returned value's type, range and meaning afterward. A single limit=huge remains invalid even though it is not duplicated.

Keep the policy in the API documentation and tests. Clients should not discover list syntax by trial and error. If the framework expects bracketed names or another array convention, document the exact wire format rather than relying on an implicit parser feature.

Compare every interpretation boundary

Trace the URL through CDN, gateway, application framework and any signing middleware. Check whether each preserves repeated names and their order. A layer that rewrites or sorts a query can change the bytes used for verification or cache selection.

Use URL Decoder to inspect sanitized encoded components, but do not repeatedly decode the entire URL without understanding the parser contract. Decoding can turn encoded separators into characters that another layer treats structurally.

The URLSearchParams reference is a useful browser-side source. Server frameworks may behave differently, so test the actual stack rather than assuming the browser representation defines every backend parser.

Include signatures and caching

If requests are signed, define the exact canonical representation of repeated parameters. Both producer and verifier must use the same rules for order, encoding and empty values. Do not let the application select a different effective value from the one the signature authenticated.

For cached responses, confirm the cache key distinguishes every input that changes the result. If the application supports repeated filters but a cache key keeps only one value, users may receive an answer for a different filter set.

Avoid solving this by sorting all query parameters indiscriminately. Some APIs treat list order as meaningful. Canonicalization is a contract, not a cosmetic cleanup step. Define when order is irrelevant and preserve it when the application requires it.

Test a small boundary matrix

Include absent, empty, single and repeated values. Test different orderings, repeated identical values, encoded names and values containing encoded separators. Also test the maximum allowed number of repeated filters.

Use only harmless requests in a staging environment you control. Verify what the application sees and what the gateway logs, with sensitive values redacted. A response alone may hide a parser disagreement until a particular authorization or cache path is exercised.

Record the expected outcome for each case: accepted list, rejected duplicate scalar or ignored unsupported field. If behavior differs by deployment environment, investigate middleware rather than updating the expected answer to match the inconsistency.

Return useful validation errors

A client should learn which parameter was repeated and which format is supported. Keep the error concise and avoid reflecting arbitrary query text into HTML. For public APIs, include an example of a valid repeated filter in the reference documentation.

Make backward compatibility explicit. If existing clients rely on last-value behavior, a stricter policy can be a breaking change. Plan a migration or versioned endpoint instead of silently changing a production request contract.

Decide whether an empty list item is meaningful. The requests tag=security&tag= and tag=security need not be equivalent. Include that distinction in validation and cache tests. Also specify whether identical repeated tags are preserved or deduplicated; silently choosing different behavior in two services makes debugging harder even when both options would be reasonable in isolation.

One URL should have one agreed meaning

Preserve repetition long enough to validate it, distinguish lists from scalars and make proxy, cache and signing rules agree. For structured-body signing, continue with JSON Canonicalization.

Advertisement
Duplicate Query Parameters: Define One API Policy | Duck Cloud