Working with Query Strings
A practical guide to working with URL query strings in JavaScript and web applications, covering parsing, building, encoding, updating parameters, repeated values, filtering, pagination, UTM parameters and common mistakes.
Query strings are one of the simplest ways to attach structured data to a URL. They are used for search, filters, sorting, pagination, analytics tracking, API options and many other forms of URL-driven state.
A query string begins after the question mark in a URL. For example, in https://example.com/products?category=books&page=2, the query string is ?category=books&page=2. It contains two parameters: category and page.
Although query strings look like ordinary text, they have their own syntax and encoding rules. Working with them correctly means understanding how to parse existing URLs, create new query strings, update individual parameters, handle repeated values and avoid encoding the same data multiple times.
What Is a Query String?
A query string is the part of a URL that begins with ? and contains additional information for the requested resource. It is commonly made up of one or more name-value pairs separated by ampersands.
https://example.com/products?category=books&page=2&sort=priceThe query string in this example is ?category=books&page=2&sort=price.
| Part | Example | Role |
|---|---|---|
| Question mark | ? | Starts the query component |
| Parameter name | category | Identifies a value |
| Equals sign | = | Separates a name from its value |
| Parameter value | books | Contains the actual data |
| Ampersand | & | Separates parameters |
Query String vs Query Parameter
The terms query string and query parameter are related but refer to different levels of the URL structure. The query string is the complete portion after the question mark, while a query parameter is an individual name-value pair inside it.
?category=books&page=2&sort=priceIn this example, the complete query string contains three parameters: category=books, page=2 and sort=price.
Why Query Strings Are Useful
Query strings are especially useful when information should be represented directly in a URL. Unlike temporary in-memory application state, URL state can be copied, bookmarked, reloaded and shared with another person.
- Search terms.
- Product and content filters.
- Sorting preferences.
- Pagination state.
- Language and locale settings.
- API request options.
- Analytics and UTM tracking.
- Shareable application state.
- Optional resource parameters.
A Basic Query String
The simplest query string contains one parameter.
https://example.com/search?q=javascriptAdditional parameters are appended with an ampersand.
https://example.com/search?q=javascript&category=web&page=2The server or client application decides what these parameters mean. URL syntax itself only defines how the query component is represented.
Reading a Query String Manually
A developer can technically parse a query string by splitting it on ampersands and equals signs. For example, a simple string could be processed manually with JavaScript.
const query = "category=books&page=2";
const pairs = query.split("&");
for (const pair of pairs) {
const [key, value] = pair.split("=");
console.log(key, value);
}This approach can appear convenient, but it quickly becomes unreliable when values contain encoded characters, repeated parameters, empty values or additional equals signs. Modern URL APIs are a much better choice for normal application code.
Use URLSearchParams
URLSearchParams is a built-in JavaScript API specifically designed for working with query parameters. It can parse existing query strings and construct new ones while handling serialization and encoding.
const params = new URLSearchParams(
"category=books&page=2&sort=price"
);
console.log(params.get("category"));
console.log(params.get("page"));
console.log(params.get("sort"));The values returned by get() are strings. If an application expects a number, boolean or another type, it should explicitly parse and validate the value.
Getting a Single Parameter
const params = new URLSearchParams(
"?category=books&page=2"
);
const category = params.get("category");
const page = params.get("page");The leading question mark is optional when creating URLSearchParams, so both ?category=books and category=books can be parsed.
What Happens When a Parameter Is Missing?
If get() is called for a parameter that does not exist, URLSearchParams returns null.
const params = new URLSearchParams(
"category=books"
);
const page = params.get("page");
console.log(page);This is useful when parameters are optional. Application code can provide a default value or handle the missing state explicitly.
const page = params.get("page") ?? "1";Checking Whether a Parameter Exists
The has() method checks whether a parameter exists.
const params = new URLSearchParams(
"category=books&page=2"
);
if (params.has("page")) {
console.log("Page parameter exists");
}has() is useful when the difference between a missing parameter and an explicitly empty parameter matters.
Reading the Complete URL
The URL class can parse a complete URL and expose its query parameters through searchParams.
const url = new URL(
"https://example.com/products?category=books&page=2"
);
console.log(url.search);
console.log(url.searchParams.get("category"));
console.log(url.searchParams.get("page"));The search property contains the query component including the question mark, while searchParams provides structured access to individual parameters.
Building a Query String
URLSearchParams can also be used to create a query string from individual values.
const params = new URLSearchParams();
params.set("category", "books");
params.set("page", "2");
params.set("sort", "price");
const queryString = params.toString();
console.log(queryString);The result can be appended to a URL when needed.
const url = `https://example.com/products?${queryString}`;Using URLSearchParams with an Object
For simple query strings, URLSearchParams can be initialized directly from an object.
const params = new URLSearchParams({
category: "books",
page: "2",
sort: "price",
});
console.log(params.toString());This is convenient when the application already has a set of named values that should become query parameters.
set() and append()
Two important URLSearchParams methods are set() and append(). They are similar, but their behavior with existing names is different.
set() creates a parameter or replaces all existing values for that name.
const params = new URLSearchParams();
params.set("tag", "javascript");
params.set("tag", "react");
console.log(params.toString());After the second set(), there is only one tag value.
append() adds another value while preserving existing values.
const params = new URLSearchParams();
params.append("tag", "javascript");
params.append("tag", "react");
console.log(params.toString());Working with Repeated Parameters
Repeated parameters are useful when one name represents multiple values.
https://example.com/tools?tag=javascript&tag=react&tag=nextjsURLSearchParams provides getAll() for retrieving all values associated with a name.
const params = new URLSearchParams(
"tag=javascript&tag=react&tag=nextjs"
);
const tags = params.getAll("tag");
console.log(tags);When designing an API, repeated parameters should be documented clearly. Some frameworks treat them as arrays, while others may use different conventions.
Deleting Parameters
The delete() method removes a parameter and all of its values.
const url = new URL(
"https://example.com/products?category=books&page=2&sort=price"
);
url.searchParams.delete("page");
console.log(url.toString());Using delete() is safer than removing text with replace() because the URL API understands the structure of the query string.
Updating Parameters
A parameter can be updated using set().
const url = new URL(
"https://example.com/products?page=2&sort=price"
);
url.searchParams.set("page", "3");
console.log(url.toString());This pattern is particularly useful for pagination and filter controls in web applications.
Sorting Query Parameters
URLSearchParams also provides sort(), which sorts parameters by their names.
const params = new URLSearchParams(
"page=2&sort=price&category=books"
);
params.sort();
console.log(params.toString());Sorting can be useful when a consistent serialized representation is desirable, for example when comparing URLs or generating deterministic strings. However, changing parameter order can matter to systems that treat the raw URL string as significant, so it should be done deliberately.
Encoding Query Values
Query values often contain spaces, punctuation or other characters that need URL encoding.
const params = new URLSearchParams();
params.set("q", "red shoes & boots");
console.log(params.toString());The URL API handles the required serialization instead of requiring the developer to manually replace spaces and special characters.
Why Manual Concatenation Is Error-Prone
A query string can be created manually with string concatenation, but this becomes increasingly difficult to maintain as the number of parameters grows.
const url =
"/products?category=" +
encodeURIComponent(category) +
"&page=" +
encodeURIComponent(String(page)) +
"&sort=" +
encodeURIComponent(sort);This can work for a small example, but it requires the developer to remember separators, encoding rules and type conversions. URLSearchParams centralizes these responsibilities.
Avoid Double Encoding
One of the most common query-string bugs is encoding a value more than once.
Original: red shoes
Encoded: red%20shoes
Double encoded: red%2520shoesThe second encoding changes the percent sign in %20 into %25. The receiving application may then see red%20shoes instead of red shoes.
Decoding Query Parameters
When URLSearchParams reads a parameter, it gives you the decoded value.
const params = new URLSearchParams(
"q=red%20shoes%20%26%20boots"
);
const query = params.get("q");
console.log(query);Because URLSearchParams handles decoding, you generally should not call decodeURIComponent on the result again.
When decodeURIComponent Is Useful
decodeURIComponent is useful when you explicitly have an individually percent-encoded component and need to decode it yourself.
const encoded = "red%20shoes%20%26%20boots";
const decoded = decodeURIComponent(encoded);
console.log(decoded);For normal query-string processing, however, URLSearchParams is usually preferable because it understands the surrounding query syntax.
Spaces, %20 and +
Spaces in query data can appear differently depending on the serialization format. Percent-encoding commonly represents a space as %20, while application/x-www-form-urlencoded uses + for spaces.
q=hello%20world
q=hello+worldThis is one reason manually replacing spaces is a poor strategy. Let the API responsible for serialization determine the appropriate representation.
Working with Empty Values
A query parameter can have an empty value.
https://example.com/search?q=URLSearchParams distinguishes this from a missing parameter.
const params = new URLSearchParams("q=");
console.log(params.has("q"));
console.log(params.get("q"));The application should decide whether an empty value is valid, equivalent to a missing value or an error.
Parameters Without an Equals Sign
Query strings can also contain a name without an explicit equals sign.
https://example.com/products?debugDifferent applications can assign different meanings to this form. When parsed by URLSearchParams, the parameter can be accessed as a name with an empty string value.
Boolean Query Parameters
Query strings contain text, so boolean values are usually represented explicitly.
https://example.com/products?featured=true&archived=falseThe application must convert those strings into actual booleans if necessary.
const value = params.get("featured");
const featured = value === "true";For more important inputs, explicit schema validation is preferable to relying on a simple comparison.
Numeric Query Parameters
Numbers also arrive as strings and should be parsed and validated before use.
const pageParam = params.get("page");
const page = Number(pageParam);
if (!Number.isInteger(page) || page < 1) {
throw new Error("Invalid page");
}Validation should also consider reasonable maximum values. A request such as page=999999999 may be syntactically valid but still be inappropriate for the application.
Query Strings for Search
Search pages are a common place to use query strings because the search term is naturally part of the URL.
https://example.com/search?q=javascript%20promisesThis makes the search state shareable and allows browser navigation to preserve previous searches.
Query Strings for Filters
Filters can also be represented through parameters.
https://example.com/products?category=books&priceMax=50&inStock=trueA frontend can update these parameters whenever the user changes a filter. The URL then becomes a serialized representation of the current view.
Query Strings for Sorting
https://example.com/products?sort=price&order=ascThe exact convention is application-specific. Some systems use sort=price:asc, while others use separate field and direction parameters.
Query Strings for Pagination
Pagination is another natural use case for query strings.
https://example.com/tools?page=3&limit=24Changing the page can update only the relevant query parameter while preserving the current filters and sorting options.
const url = new URL(window.location.href);
url.searchParams.set("page", "3");
window.history.pushState({}, "", url);Framework-specific routing APIs are often preferable in a React or Next.js application, but the underlying URL manipulation works the same way.
Query Strings for API Requests
APIs frequently use query strings for optional request parameters such as filtering, pagination and sorting.
GET /api/products?category=books&page=2&limit=20 HTTP/1.1The server can inspect these parameters and use them to determine which records to return.
Query Strings and Arrays
There is no universal syntax for representing arrays in a query string. Common approaches include repeated names, comma-separated values and bracket notation.
| Convention | Example |
|---|---|
| Repeated values | ?tag=js&tag=react&tag=next |
| Comma-separated | ?tag=js,react,next |
| Bracket notation | ?tag[]=js&tag[]=react |
| Indexed notation | ?tag[0]=js&tag[1]=react |
Repeated parameters are particularly straightforward with URLSearchParams because append() and getAll() explicitly support multiple values.
Query String Iteration
URLSearchParams is iterable, which makes it convenient to inspect every parameter.
const params = new URLSearchParams(
"category=books&page=2&sort=price"
);
for (const [key, value] of params) {
console.log(key, value);
}This is useful for debugging, logging and generic URL-processing utilities.
keys(), values() and entries()
URLSearchParams also exposes iterators for keys, values and complete entries.
for (const key of params.keys()) {
console.log(key);
}
for (const value of params.values()) {
console.log(value);
}
for (const [key, value] of params.entries()) {
console.log(key, value);
}Converting Query Parameters to an Object
For simple cases, query parameters can be converted to an object.
const params = new URLSearchParams(
"category=books&page=2"
);
const values = Object.fromEntries(params);
console.log(values);This approach is convenient when every parameter name is unique. Repeated parameter names require additional handling because a plain object cannot represent multiple values under the same key without using an array.
Building a URL from Existing Query Parameters
When modifying a URL, it is often better to start with URL rather than manually extracting the query string.
const url = new URL(
"https://example.com/products?category=books&page=2"
);
url.searchParams.set("sort", "price");
console.log(url.toString());This preserves the existing URL structure while adding or updating the desired parameter.
Relative URLs and Query Strings
Query strings can also be used with relative URLs.
/products?category=books&page=2Relative URLs are common in navigation because the browser resolves them against the current origin.
Query Strings and URL Fragments
A URL can contain both a query string and a fragment. The query comes before the fragment.
https://example.com/docs?lang=en#installationThe query component is ?lang=en, while the fragment is #installation. The fragment is not part of the query string.
Query Strings and UTM Parameters
UTM parameters are ordinary query parameters that follow a widely used naming convention for marketing analytics.
https://example.com/pricing?utm_source=newsletter&utm_medium=email&utm_campaign=launch| Parameter | Typical purpose |
|---|---|
| utm_source | Identifies the traffic source |
| utm_medium | Identifies the marketing medium |
| utm_campaign | Identifies the campaign |
| utm_term | Commonly identifies a paid-search term |
| utm_content | Distinguishes links or creatives within a campaign |
Because UTM parameters are part of the query string, they can be read and manipulated using the same URLSearchParams API as any other parameter.
Query Strings and Browser History
When query parameters represent application state, changing them through normal navigation can create useful browser history entries.
This allows users to move backward and forward between searches, filters or pagination states. Whether every change should create a history entry depends on the application. For example, a search submission may reasonably create a new history entry, while every keystroke in a live search field may not.
Query Strings in React Applications
React applications frequently use query strings to keep filters, sorting and pagination synchronized with the URL.
const params = new URLSearchParams();
params.set("category", category);
params.set("sort", sort);
params.set("page", String(page));
const href = `/tools?${params.toString()}`;A URL-driven state model makes the current view shareable and allows the browser's navigation system to participate in state management.
Query Strings in Next.js
Next.js applications commonly use query strings for search pages, filtering, sorting and pagination. In the App Router, server-rendered pages can receive search parameters, while client components can read the current URL using routing APIs such as useSearchParams.
"use client";
import { useSearchParams } from "next/navigation";
export default function ToolsPage() {
const searchParams = useSearchParams();
const category = searchParams.get("category");
const page = searchParams.get("page");
return null;
}When updating the URL in Next.js, use the routing APIs appropriate for the application rather than directly modifying window.history when framework navigation behavior also needs to be coordinated.
Validating Query String Data
Query parameters come from the URL and should generally be considered untrusted input. A parameter that looks like a number, enum or identifier still needs validation before being used by application logic.
const sort = params.get("sort");
const allowedSorts = ["name", "price", "date"];
if (sort !== null && !allowedSorts.includes(sort)) {
throw new Error("Invalid sort parameter");
}This is especially important when query parameters affect database queries, filesystem paths, redirects, authorization or resource limits.
Do Not Trust Query Parameters for Authorization
A query parameter can be changed by the user. It should never be treated as proof that the user has permission to perform an operation.
https://example.com/account?userId=123The presence of userId=123 does not establish that the current user is allowed to access account 123. Authorization must be enforced independently on the server.
Sensitive Data in Query Strings
URL encoding does not make sensitive query data private. Percent-encoding only changes the representation of the value and can be reversed.
Query Strings and Caching
Query strings can affect HTTP and application caching because different query strings can represent different request URLs.
/products?page=1
/products?page=2
/products?page=3If a CDN or application cache includes the query string in its cache key, these URLs can produce separate cached entries. Cache configuration should therefore account for which parameters actually affect the response.
Query Strings and SEO
Query strings can create useful indexable URLs, but they can also produce many URL variations for the same content.
https://example.com/article
https://example.com/article?utm_source=email
https://example.com/article?utm_source=socialTracking parameters may create multiple URL representations of the same page. Websites should consider canonicalization and other SEO controls when query parameters do not represent separate content.
Canonical Query Strings
Some applications benefit from producing a consistent representation of equivalent query strings. This can include consistent parameter names, ordering, casing and handling of default values.
Canonicalization can be useful for caching, URL comparison, analytics and SEO, but it should be implemented according to the application's actual semantics. Two query strings that look similar are not automatically equivalent.
Removing Default Parameters
If an application uses page=1 as the default page, it may choose to omit page=1 from the canonical URL.
https://example.com/products
https://example.com/products?page=1Whether these should be treated as equivalent depends on the application. If page=1 is simply the default representation, keeping the URL shorter can simplify canonicalization.
Debugging Query Strings
When a URL does not behave as expected, inspect the actual query parameters rather than relying only on what the interface appears to show.
const url = new URL(window.location.href);
console.log("Raw query:", url.search);
for (const [key, value] of url.searchParams) {
console.log(key, value);
}This can reveal missing parameters, unexpected values, repeated names and encoding problems.
Common Query String Bugs
- Forgetting the question mark before the first parameter.
- Using commas or spaces instead of ampersands between parameters.
- Failing to encode special characters in values.
- Encoding a value twice.
- Calling decodeURIComponent on values already decoded by URLSearchParams.
- Using set() when append() is required for repeated values.
- Assuming query parameters are automatically numbers or booleans.
- Ignoring duplicate parameter names.
- Using different array conventions between API endpoints.
- Putting secrets into query strings.
- Trusting query parameters for authorization.
- Ignoring query strings when designing cache behavior.
A Practical Query String Workflow
A reliable workflow starts by treating the URL as structured data rather than an ordinary string.
- Parse the URL with the URL API when working with a complete URL.
- Use searchParams to access individual query parameters.
- Use get() for one value and getAll() for repeated values.
- Use set() when replacing a value.
- Use append() when intentionally adding another value.
- Use delete() to remove a parameter.
- Let URLSearchParams handle serialization and encoding.
- Validate values before using them in application logic.
- Keep sensitive information out of the query string.
- Define consistent conventions for arrays, booleans, pagination and sorting.
Example: Building a Filtered Product URL
A product page might need to preserve a category, price limit, sorting mode and page number.
const params = new URLSearchParams();
params.set("category", "books");
params.set("maxPrice", "50");
params.set("sort", "price");
params.set("page", "2");
const url = `/products?${params.toString()}`;If the user changes the sort order, the application can update only that parameter while keeping the other state intact.
params.set("sort", "rating");
const updatedUrl = `/products?${params.toString()}`;Example: Preserving Existing Parameters
Sometimes a feature needs to add one parameter without removing the others already present in the URL.
const url = new URL(window.location.href);
url.searchParams.set("view", "grid");
console.log(url.toString());This pattern is useful for interfaces where several independent controls contribute to the same URL state.
Query Strings vs Request Bodies
Query strings are best suited to relatively small pieces of request metadata such as filtering, sorting and pagination. They are not a universal replacement for HTTP request bodies.
| Data | Common location |
|---|---|
| Search term | Query string |
| Filter | Query string |
| Sort order | Query string |
| Pagination | Query string |
| Complex object for creation | Request body |
| Large structured payload | Request body |
| Secret credential | Dedicated secure mechanism |
Query String Best Practices
- Use URLSearchParams instead of manually splitting and joining query strings.
- Use the URL API when working with complete URLs.
- Keep parameter names consistent across related endpoints.
- Document whether parameters are required or optional.
- Document default values.
- Define how repeated parameters and arrays are represented.
- Validate all externally supplied values.
- Avoid putting sensitive data into URLs.
- Avoid double encoding and unnecessary manual decoding.
- Consider SEO and caching when query parameters create multiple URL variants.
- Keep URL-driven application state limited to information that is useful to preserve and share.
Frequently Asked Questions
What is a query string?
A query string is the portion of a URL that begins with ? and contains additional request data, usually as name-value pairs separated by ampersands.
What is the easiest way to work with query strings in JavaScript?
For most JavaScript applications, URLSearchParams is the appropriate API for creating, reading, updating and deleting query parameters.
What is the difference between set() and append()?
set() creates or replaces all existing values for a parameter name, while append() adds another value and preserves existing values with the same name.
How do I get all values for a repeated query parameter?
Use URLSearchParams.getAll(). For example, tag=javascript&tag=react can be read as an array of values with getAll('tag').
Are query parameters automatically numbers and booleans?
No. Query parameters are represented as text. Applications should explicitly parse and validate numeric, boolean and other typed values.
Should I manually encode query parameters?
Usually not when using URLSearchParams or the URL API. These APIs handle serialization and encoding for normal application use.
Can query strings contain sensitive information?
Technically yes, but sensitive information such as passwords, access tokens and private credentials should not be placed in URLs because URLs can be stored in browser history, logs, analytics systems and other infrastructure.
Helpful Query String Tools
A URL Query String Parser is useful for inspecting an existing URL and viewing its individual parameters and values. A Query Parameter Builder helps construct query strings from structured data, while a URL Builder can combine a query string with the rest of a URL. A URL Parser is useful when you need to inspect all URL components, not just the query. For marketing links, a UTM Builder can generate consistent tracking parameters without manually assembling the query string.
Conclusion
Query strings are a small but important part of modern web development. They allow URLs to carry search terms, filters, sorting options, pagination state, API parameters and analytics information in a form that can be shared and preserved by the browser.
For JavaScript applications, URL and URLSearchParams provide structured APIs for working with query strings. They are preferable to manually splitting strings or concatenating parameter values because they handle parsing, encoding and repeated parameters more reliably.
The most important habits are to treat query parameters as untrusted string data, encode them through the appropriate URL APIs, validate values before using them, define clear conventions for repeated parameters and arrays, and keep sensitive information out of URLs.
Once query strings are treated as structured URL data rather than ordinary text, tasks such as building filters, preserving application state, generating API requests and debugging URLs become considerably easier to manage.