Converting Objects to Query Strings and Vice Versa

In Brief
Use URLSearchParams to encode and parse ordinary query strings, then decide explicitly how your application represents repeated keys, arrays, empty values, and reconstructed types. Query‑string values are strings, and duplicate keys carry meaning, so a convenient object conversion should not silently discard ordering or collapse several values into one.
As the lines between front‑end and full‑stack development continue to blur, more and more applications are written entirely with JavaScript from the 'actual' front‑end to complete server‑side APIs and everything in between. It has long been the case that, as front‑end engineers, we need to be able to handle and react to query parameters in the URL to handle search parameters or persist state via the URL. Now and going forwards, more than ever, converting objects to query strings (and vice versa) is an essential skill.
In this article, I intend to explore how we can convert objects into query strings, how to parse query strings back into objects, and ‑ in particular ‑ how this applies when working with Next.js. By the end, you should have a solid understanding of handling query parameters effectively.
What is a Query String?
Starting with the absolute basics, a query string is a part of a URL which contains key‑value pairs used to pass data between pages or to an API. It begins with a ? and separates parameters with &. For example:
https://example.com/search?query=JavaScript&page=2In this URL, query=JavaScript and page=2 are parameters that can be read and used by the server or client‑side application. Query strings are commonly used in APIs, search functionality, and client‑side state management.
Converting an Object to a Query String
JavaScript provides several ways to construct query strings from objects, depending on the level of control needed.
Using URLSearchParams
The URLSearchParams API (which has been available in browsers for eleven years now) simplifies working with query strings:
const params = { search: "JavaScript", page: "2", sort: "asc" };const queryString = new URLSearchParams(params).toString();console.log(queryString); // Output: "search=JavaScript&page=2&sort=asc"This method automatically encodes special characters and produces a properly formatted query string. The record passed to the constructor contains strings; use append() when a key has more than one value.
Custom Function for More Control
If you need more flexibility, you could implement a custom function like this:
type QueryValue = string | number | boolean;type QueryObject = Record<string, QueryValue | QueryValue[]>;const objectToQueryString = (obj: QueryObject): string => { const query = new URLSearchParams(); Object.entries(obj).forEach(([key, value]) => { const values = Array.isArray(value) ? value : [value]; values.forEach((item) => { query.append(key, String(item)); }); }); return query.toString();};const params = { search: "JavaScript", page: 2, sort: "asc" };console.log(objectToQueryString(params));// Output: "search=JavaScript&page=2&sort=asc"Here, the function converts an object into a query string and uses append() for every value. Scalar values produce one pair, while arrays produce repeated keys without discarding their order.&. This provides greater control over the formatting, ensuring that special characters are encoded correctly and allowing for modifications such as handling arrays or nested objects differently.
Parsing a Query String into an Object
To extract parameters from a query string and convert them into an object, JavaScript also provides built‑in utilities.
Using URLSearchParams
The simplest way to parse query strings is to use URLSearchParams again, like this:
const queryString = "search=JavaScript&page=2&sort=asc&tag=css&tag=javascript";const searchParams = new URLSearchParams(queryString);const params: Record<string, string | string[]> = {};searchParams.forEach((value, key) => { const current = params[key]; if (current === undefined) { params[key] = value; } else { params[key] = Array.isArray(current) ? [...current, value] : [current, value]; }});console.log(params);// Output: { search: "JavaScript", page: "2", sort: "asc", tag: ["css", "javascript"] }The values are strings. In this example, repeated keys are represented as string arrays rather than silently retaining only the final value. Convert values to other types only when the application's input contract requires it.
Custom Function for Parsing
A small wrapper can define how repeated values are represented without reimplementing URL parsing and decoding:
type QueryObject = Record<string, string | string[]>;const queryStringToObject = (query: string): QueryObject => { const searchParams = new URLSearchParams(query.startsWith("?") ? query.slice(1) : query); const result: QueryObject = {}; searchParams.forEach((value, key) => { const current = result[key]; if (current === undefined) { result[key] = value; } else { result[key] = Array.isArray(current) ? [...current, value] : [current, value]; } }); return result;};console.log(queryStringToObject("?search=JavaScript&page=2&tag=css&tag=javascript"));// Output: { search: "JavaScript", page: "2", tag: ["css", "javascript"] }This keeps parsing and decoding with URLSearchParams, while the wrapper explicitly represents repeated keys as arrays. More complex structures still need a documented application‑specific encoding contract.
Handling Query Strings in the Next.js Pages Router
In the Next.js Pages Router, query strings are commonly used for search parameters and URL‑based state. The router exposes utilities for reading and updating them.
Accessing Query Parameters in Next.js
In the Pages Router, Next.js provides the useRouter hook, which you can use like this:
import { useRouter } from "next/router";type QueryValue = string | string[] | undefined;const firstQueryValue = (value: QueryValue) => { return Array.isArray(value) ? value[0] : value;};const SearchPage = () => { const router = useRouter(); const search = firstQueryValue(router.query.search); const page = firstQueryValue(router.query.page); const sort = firstQueryValue(router.query.sort); return ( <div> <h1>Search Results</h1> <p>Search Term: {search}</p> <p>Page: {page}</p> <p>Sort Order: {sort}</p> </div> );};export default SearchPage;Query parameters are available in router.query as an object, but ‑ as with using URLSearchParams ‑ each value can be a string, a string array, or undefined until the router is ready. The example selects the first value explicitly because the page expects one value per field.
Updating Query Parameters in Next.js
To update the URL with new query parameters without reloading the page, we can define a custom hook around router.push:
import { useRouter } from "next/router";type QueryValue = string | string[] | undefined;type QueryUpdates = Record<string, QueryValue>;const useUpdateQuery = () => { const router = useRouter(); return (newParams: QueryUpdates) => { void router.push( { pathname: router.pathname, query: { ...router.query, ...newParams }, }, undefined, { shallow: true }, ); };};This allows us to modify the query string dynamically without causing a full page refresh.
Wrapping Up
Converting objects to query strings and parsing query strings into objects is a key technique in JavaScript, especially when working with URLs. The URLSearchParams API provides a simple way to handle this, but custom functions allow for greater flexibility.
In a Pages Router component, useRouter exposes string, string‑array and temporarily undefined values, while a custom hook can update the URL without a full page reload.
Key Takeaways
- Query strings enable passing key‑value data via URLs.
- The
URLSearchParamsAPI simplifies converting objects to query strings and vice versa. - Custom functions provide more control, such as handling arrays and complex structures.
- Query parameters in Next.js are accessed via
router.queryand modelled as string, string array, orundefined. - Updating query parameters in Next.js can be done using
router.push()with shallow routing.
Understanding these techniques allows you to handle query parameters more efficiently, making it easier to manage search functionality and URL‑based state in JavaScript and Next.js applications.