qs: Querystring Parser and Stringifier
qs is a robust JavaScript library for parsing and stringifying URL query strings, with comprehensive support for nesting objects and arrays. It is currently on version 6.15.1 and maintains a steady release cadence with a focus on stability and security patches. Key differentiators include its configurable depth limits for parsing, the ability to handle URI-encoded strings, and built-in protections against prototype pollution through options like `plainObjects` and `allowPrototypes` (which is dangerous if enabled). Unlike the native `querystring` module in Node.js, `qs` offers more advanced features like array indexing and custom parsing/stringifying logic, making it suitable for complex data structures often found in web applications.
Common errors
-
TypeError: qs.parse is not a function
cause Attempting to use `qs.parse` or `qs.stringify` as a named import (e.g., `import { parse } from 'qs';`) instead of accessing it as a property of the default exported `qs` object.fixFor CommonJS, use `const qs = require('qs'); const obj = qs.parse('...');`. For ESM, use `import qs from 'qs'; const obj = qs.parse('...');`. -
Input depth exceeded depth option of X and strictDepth is true
cause The parsed querystring exceeded the maximum allowed nesting depth (`depth` option), and the `strictDepth` option was set to `true`, causing an error to be thrown instead of silently truncating the nested keys.fixEither increase the `depth` option to accommodate the expected nesting level (`qs.parse(str, { depth: <new_depth> })`) or adjust the input to reduce nesting. If truncation is acceptable, remove `strictDepth: true`. -
Unexpected parsed object structure, e.g., `{ 'a[b]': 'c' }` instead of `{ a: { b: 'c' } }` or `{ 'a.b': 'c' }` instead of `{ a: { b: 'c' } }`cause This often occurs due to misunderstanding the `allowDots` or `depth` options. For instance, `allowDots` defaults to `true` in v6, preventing dot notation from creating nested objects by default. The `depth` limit can also cause key concatenation.fixTo parse `a.b=c` into `{ a: { b: 'c' } }`, use `qs.parse(str, { allowDots: false })`. To handle deep nesting, adjust the `depth` option (e.g., `qs.parse(str, { depth: 10 })`).
Warnings
- breaking The `allowDots` option's default value changed from `false` to `true` in `qs` v6.0.0. This means by default, query strings like `a.b=c` will now parse into `{ 'a.b': 'c' }` instead of `{ a: { b: 'c' } }` in previous versions. If you relied on dot notation for nested objects, you might need to explicitly set `allowDots: false` or update your parsing logic.
- breaking The default `arrayLimit` for parsing arrays changed multiple times across major versions (e.g., to 20 in v5, and later). If your application parses arrays with more than the default limit of items (e.g., `a=1&a=2&...&a=21`), elements beyond the limit will be truncated or ignored, potentially leading to data loss if not handled.
- gotcha By default, `qs.parse` limits object nesting depth to 5 to prevent potential Denial of Service (DoS) attacks from excessively deep query strings. If a query string exceeds this depth, subsequent nested keys are concatenated into a single key, leading to unexpected parsed object structures.
- breaking Setting `allowPrototypes: true` in `qs.parse` or `qs.stringify` can introduce severe prototype pollution vulnerabilities. This option allows user-controlled input to modify properties on `Object.prototype`, which can impact all objects in the application and lead to remote code execution or other critical security flaws.
Install
-
npm install qs -
yarn add qs -
pnpm add qs
Imports
- qs
import qs from 'qs';
const qs = require('qs'); - qs.parse
import { parse } from 'qs';const { parse } = require('qs'); - qs.stringify
import { stringify } from 'qs';const qs = require('qs'); const stringify = qs.stringify;
Quickstart
const qs = require('qs');
const assert = require('assert');
// Parsing a simple querystring
let obj = qs.parse('a=c&b=d');
assert.deepEqual(obj, { a: 'c', b: 'd' });
// Stringifying an object
let str = qs.stringify({ a: 'c', b: 'd' });
assert.equal(str, 'a=c&b=d');
// Parsing a nested object
obj = qs.parse('foo[bar]=baz');
assert.deepEqual(obj, { foo: { bar: 'baz' } });
// Parsing an array
obj = qs.parse('a=b&a=c');
assert.deepEqual(obj, { a: ['b', 'c'] });
// Demonstrating depth limit (default 5)
const deepString = 'a[b][c][d][e][f][g]=h';
obj = qs.parse(deepString);
assert.deepEqual(obj, {
a: {
b: { c: { d: { e: { f: { '[g]': 'h' } } } } }
}
});
console.log('All assertions passed!');