finds.dev← search

// the find

ljharb/qs

★ 8,943 · JavaScript · BSD-3-Clause · updated Sep 2026

A querystring parser and serializer with nesting support

qs parses and stringifies query strings with nested object and array syntax, such as `a[b][c]=d`, and it was long the default query parser in Express 4. It suits anyone who needs bracket notation on the wire and cares how hostile input is handled.

- The limits are on by default: depth 5, 1000 parameters, arrayLimit 20, and prototype-named keys dropped unless opted in. `strictDepth` and `throwOnLimitExceeded` let a caller reject oversized input instead of quietly truncating it, which is the choice a security-minded caller usually wants.

- Parse and stringify share one option vocabulary, so `arrayFormat`, `commaRoundTrip`, `encodeValuesOnly`, and `format` can be set once and round-trip cleanly. The README documents the edge cases, such as single-item arrays under comma format, rather than leaving them to surprise people.

- The `encoder` and `decoder` hooks receive a `type` argument (key or value), which is how qs-iconv handles Shift_JIS without the core package carrying charset tables. Extension goes through a hook instead of a fork.

- The README examples are written as `assert` calls, so the documented behavior is precise about edge cases like empty strings versus `null` and where `strictNullHandling` changes the output.

- The limits are per parameter, not per request. With `comma: true`, one `&`-delimited value can expand into arbitrarily many elements, and `throwOnLimitExceeded` checks each array separately, so the real bound has to come from the HTTP layer. The README says this, but a caller who sets `parameterLimit` and stops there is not protected.

- `arrayLimit` changes the container type instead of capping size. Input like `a[100]=b` quietly produces `{ a: { '100': 'b' } }`, so code that branches on `Array.isArray` takes the wrong path with no error unless `throwOnLimitExceeded` is on.

- Option interactions are subtle. `decodeDotInKeys` implies `allowDots` and throws if `allowDots` is false, `strictMerge` changes the shape of `a[b]=c&a=d`, and the 6.14.1 and 6.15.2 releases changed how unbalanced brackets parse. An upgrade can change output for malformed input, so pin the version and test those inputs.

- Most dropped or folded input is silent by default. Parameters past `parameterLimit` are ignored, prototype-named keys vanish, and input past `depth` is folded into a literal key such as '[g][h][i]'. That is the safer default for a public-facing parser, but it makes bugs look like missing data.

View on GitHub →

// want more like this?

We dig through GitHub every week and send a few repos picked for what you actually care about — each with an honest take like this one.

Get finds in your inbox → Search again →