Library / SDK
sindresorhus/query-string avatar
sindresorhus/query-string

query-string: parsing and stringifying URL query strings in JavaScript

Parse and stringify URL query strings

6,909 stars456 forksJavaScriptMIT

At a glance

What is it?
sindresorhus/query-string is a small npm package for turning query strings into objects and back. It solves the array-format problem that URLSearchParams leaves open, at the cost of a dependency and a deliberate refusal to guess types.
Who is it for?
Adopt query-string when your query strings carry repeated keys, bracket or comma array notation, or mixed types that you want decoded deterministically, and when you can accept a dependency and an ESM-only package. Do not adopt it if you only need to read one or two parameters in a modern browser, where the README itself points you at URLSearchParams.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 28 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The gap query-string fills between URLSearchParams and a full query parser

The browser already ships URLSearchParams, and the README says so plainly: consider using it for simple use cases, since it is a native API that handles basic query string operations. The problem is what counts as simple. URLSearchParams gives you a flat list of key and value pairs. It does not tell you whether foo=1&foo=2 is an array, whether foo[]=1&foo[]=2 is one, or whether foo=1,2,3 is three values or one string containing commas. Every server framework picks its own convention, and the client has to match it.

query-string exists to make that convention explicit. You pass an arrayFormat option and the package parses according to it. It also parses the fragment, not just the search string: the README shows queryString.parse(location.hash) returning {token: 'bada55cafe'} from '#token=bada55cafe', with leading ? or # ignored. That matters for OAuth-style implicit flows where the token arrives in the hash and never reaches the server.

The audience is JavaScript developers who control both ends of a URL, or who have to read URLs produced by a framework with a known array convention. It is not aimed at people who need one parameter out of window.location.search.

How parse and stringify actually work

The package is small by design. The published files list is index.js, index.d.ts, base.js and base.d.ts, and the runtime dependencies are decode-uri-component, filter-obj and split-on-first. There is no parser generator and no schema engine.

parse(string, options) splits the input on the first ? or #, splits the remainder on &, then on the first = in each pair, and decodes components with decode-uri-component unless decode is false. The returned object is created with Object.create(null), so it has no prototype. That is a deliberate choice: a query string containing __proto__ cannot reach Object.prototype through the parsed result. Sorting is on by default, controlled by the sort option, which accepts a custom function or false.

stringify(parsed) reverses the process and returns a string without a leading question mark. The README example shows the round trip: parse '?foo=bar', change the object, stringify it back to 'foo=unicorn&ilike=pizza', then assign it to location.search, which prepends the question mark itself.

The interesting part is the arrayFormat option on parse, which accepts 'bracket', 'index', 'comma', 'separator', 'bracket-separator', 'colon-list-separator' and 'none'. The default is 'none', meaning duplicate keys become an array. The 'bracket-separator' mode is the most unusual: it only treats a key as an array when the key is explicitly written with brackets, so foo[]=1|2|3 with arrayFormatSeparator set to '|' becomes ['1', '2', '3'], while a bare bar=fluffy stays a string. That distinction is exactly what a general-purpose parser cannot make for you.

Installing query-string and parsing a first URL

Installation is one command. The README warns in a callout to remember the hyphen and not to install the deprecated querystring package, which is a different, Node-core-derived project with a similar name.

bash
npm install query-string

The package is type: module and its exports map points types at ./index.d.ts and default at ./index.js, so it is ESM-only. The engines field requires Node 18 or newer. A CommonJS require() will not work without a loader or an interop step, and the README does not document one.

A first real use is reading a search string and rebuilding it. This example follows the README's usage section closely:

js
import queryString from 'query-string';

const parsed = queryString.parse('?foo=bar&tag=red&tag=blue');
console.log(parsed);
//=> {foo: 'bar', tag: ['red', 'blue']}

parsed.foo = 'unicorn';
const stringified = queryString.stringify(parsed);
console.log(stringified);
//=> 'foo=unicorn&tag=red&tag=blue'

With the default arrayFormat of 'none', the repeated tag key becomes an array, and stringify writes it back as duplicate keys. If your server expects brackets instead, pass the matching format on the way in:

js
queryString.parse('foo[]=1&foo[]=2&foo[]=3', {arrayFormat: 'bracket'});
//=> {foo: ['1', '2', '3']}

For browser use, the README states that the package targets the latest version of Chrome, Firefox and Safari. There is no build step for those targets; you import the module and your bundler handles it.

The types option, and why parseNumbers is off by default

By default every parsed value is a string. parseNumbers and parseBooleans are both false unless you turn them on, which is the safer default: a value like 01234 is a zip code, not the number 1234, and a blanket conversion would destroy it.

The types option is the answer to that tension. It takes an object mapping parameter names to 'boolean', 'string', 'number' or 'string[]', and the README states it takes precedence over parseNumbers, parseBooleans and arrayFormat. The documentation gives a phone number as the motivating case:

js
queryString.parse('?phoneNumber=%2B380951234567&id=1', {
  parseNumbers: true,
  types: {
    phoneNumber: 'string'
  }
});
//=> {phoneNumber: '+380951234567', id: 1}

Here parseNumbers is on globally, but phoneNumber is pinned to string so the leading plus survives. The boolean type is broader than it sounds: the README notes it converts '0' and '1' to booleans and treats a valueless key such as ?flag as true. You can also supply a function instead of a type name, which receives the raw string and returns whatever you want; with array formats the function runs per element.

This is the strongest reason to choose the package over URLSearchParams. Per-parameter typing is not something the native API offers, and doing it by hand means writing the same coercion code in every project.

Where query-string is the wrong tool

The README's own tip is the first limitation: for simple cases, use URLSearchParams. If you need one value out of a URL and you are in a browser, adding a dependency to get it is a poor trade.

The second limitation is the ESM-only packaging. The exports map has no require condition, and the engines field demands Node 18 or newer. A project still on CommonJS, or on an older Node line, cannot import it without a workaround that the README does not describe.

Third, the package parses. It does not validate. A query string is attacker-controlled input in most web applications, and nothing here checks that foo is one of three allowed values, that a number is within range, or that an array has a bounded length. You still need a schema validator on top. Do not read the Object.create(null) result as a security boundary; it removes one prototype-pollution path, not the problem of untrusted input.

Fourth, the array format is a guess you have to get right. If you parse with 'comma' a URL that your server wrote with duplicate keys, you get a single string containing commas and no error. The package cannot detect the mismatch, and the README does not offer a way to parse without declaring a format.

Finally, there is no documented rollback or migration guidance for the array-format options, and the README does not discuss what happens to existing URLs if you change arrayFormat in a live application. That is a decision you make, not one the library makes for you.

query-string versus qs and URLSearchParams

The closest alternative in the same niche is qs, which is not mentioned anywhere in this repository's README or package.json, so the comparison below is drawn from what this package documents about itself rather than from qs documentation.

The real difference is scope. query-string exposes two functions and a fixed set of array conventions you select with arrayFormat. It has three runtime dependencies and a published file list of four files. Its API surface is parse, stringify and their options. A library in the qs family typically goes further, supporting nested object structures and bracket notation with depth, which means a larger parser and a different set of edge cases. If your query strings encode nested objects, query-string's flat key-and-value model with array formats will not represent them, and you need a parser built for that.

Against URLSearchParams the trade is the reverse. The native API ships with the platform, has no install step, and is fast because it is not JavaScript. It also has no types option, no arrayFormat, and no built-in sort control. query-string adds those three things and a dependency tree in exchange.

A reasonable split: use URLSearchParams for reading a couple of parameters, and query-string when the same URL has to round-trip through parse and stringify without losing the shape of repeated keys.

Maintenance, licence and what an upgrade costs

The repository is not archived, and the last push was on 2026-09-01. The most recent release listed is v9.5.1 on the same date, following v9.5.0 on 2026-08-06 and v9.4.1 on 2026-06-28. Releases in the 9.x line have been frequent enough that pinning a minor version is worth doing.

The package is MIT licensed, which permits commercial and private use with the licence text retained. That is a statement about the licence file, not legal advice; check how your organisation handles third-party licence attribution.

The upgrade cost is dominated by the ESM-only packaging. Because exports has no require entry, a major version bump that changes the exports map is the kind of change that breaks a build rather than a test. The dependency set is small (decode-uri-component, filter-obj, split-on-first), so supply-chain surface is limited, but those three packages move independently of query-string and their own major bumps are worth watching when you update the lockfile. The repository includes a benchmark.js and an ava test suite, so the maintainers do measure parsing performance, though no numbers are published in the README.

Editorial conclusion

Adopt query-string when your query strings carry repeated keys, bracket or comma array notation, or mixed types that you want decoded deterministically, and when you can accept a dependency and an ESM-only package. Do not adopt it if you only need to read one or two parameters in a modern browser, where the README itself points you at URLSearchParams. Before committing, check three things: that your toolchain handles an ESM-only package on Node 18 or newer, that the arrayFormat you pick matches what your server already emits, and that the query strings you parse are not attacker-controlled input you plan to feed straight into application logic.

Frequently asked questions

What is a query string, and what does query-string do with one?

A query string is the part of a URL after the question mark, made of key and value pairs separated by ampersands. This package parses that text into an object and stringifies an object back into that text, ignoring a leading ? or # so you can pass location.search or location.hash directly.

How do I add a query string to a URL with query-string?

Build or modify an object, pass it to queryString.stringify, and assign the result to location.search. The README notes that location.search prepends the question mark itself, so the string returned by stringify does not include one.

How do I access query string values in JavaScript with this package?

Call queryString.parse on location.search or location.hash and read the resulting object. The returned object is created with Object.create(null), so it has no prototype; values are strings unless you enable parseNumbers, parseBooleans or the types option.

What is %20 in a query string?

It is the percent-encoded form of a space. query-string decodes URL components with decode-uri-component by default, so a parsed value containing %20 comes back as a space unless you pass decode: false.

What is a query string parameter?

It is one key and value pair inside a query string, such as foo=bar in ?foo=bar. query-string parses each pair into a property on the returned object, and repeated keys become an array under the default arrayFormat of 'none'.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. sindresorhus/query-string on GitHub
For maintainers

Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sindresorhus-query-string.svg)](https://hysenlabs.com/projects/sindresorhus-query-string)
Community notes

Community notes