Query parameters are the part of a URL that comes after the ? mark. They're how web applications pass data through URLs—search queries, filters, pagination, tracking codes, and more. Understanding how they work is essential for web development, API design, and debugging.
Key Takeaways
- 1Query strings start with ? and contain key=value pairs separated by &
- 2Use URLSearchParams in JavaScript for safe parameter handling
- 3Always encode parameter values to handle special characters
- 4The same key can appear multiple times for array-like data
- 5Keep URLs under 2,000 characters for maximum browser compatibility
Anatomy of a Query String
Before diving into code, let's understand the structure of query strings. This section breaks down the syntax rules and special cases you'll encounter, giving you the foundation to work with any query string confidently.
A URL with query parameters has this structure:
https://shop.example.com/products?category=shoes&color=red&size=10Breaking this down, the table below maps each component to its role in the query string:
| Part | Value | Purpose |
|---|---|---|
| Base URL | https://shop.example.com/products | The resource location |
| Query separator | ? | Marks the start of parameters |
| First parameter | category=shoes | Key-value pair |
| Parameter separator | & | Separates parameters |
| Second parameter | color=red | Another key-value pair |
| Third parameter | size=10 | Numbers work too |
Syntax Rules
Query parameters follow these rules:
- Start with
?— The question mark separates the path from the query string - Key-value pairs — Each parameter has a key and value joined by
= - Separated by
&— Multiple parameters are joined with ampersands - Order doesn't matter —
?a=1&b=2equals?b=2&a=1(usually) - Values are optional —
?flagor?flag=are both valid - Keys can repeat —
?color=red&color=blueis valid
Special Cases
Not all query strings follow the simple key=value pattern:
# Key without value (boolean flag)
?debug
?debug=
# Empty value (explicitly empty)
?query=
# Multiple values for same key
?tag=javascript&tag=typescript&tag=react
# Array notation (common convention)
?ids[]=1&ids[]=2&ids[]=3
# Nested objects (framework-specific)
?filter[status]=active&filter[type]=premium
# Comma-separated values (sometimes used)
?colors=red,green,blueThese special cases show the flexibility (and complexity) of query strings. Boolean flags, repeated keys, bracket notation, and comma-separated values are all valid, but different servers handle them differently. When designing APIs, document which conventions you support.
Now that you understand the structure, let's see how to work with query parameters programmatically, starting with JavaScript.
Working with Query Parameters in JavaScript
The URLSearchParams API is your primary tool for query string manipulation in JavaScript. This section covers reading, adding, modifying, and building query strings from scratch, with patterns you'll use daily.
Modern JavaScript provides the URLSearchParams API for safe, easy query string manipulation. It handles encoding automatically and provides methods for all common operations.
Reading Parameters
// From the current page URL
const params = new URLSearchParams(window.location.search);
// From any URL
const url = new URL('https://example.com/search?q=hello&page=2');
const params = url.searchParams;
// Get a single value
const query = params.get('q'); // "hello"
const page = params.get('page'); // "2" (always a string!)
const missing = params.get('foo'); // null
// Check if a parameter exists
if (params.has('debug')) {
console.log('Debug mode enabled');
}
// Get all values for a key (when key appears multiple times)
const tags = params.getAll('tag'); // ["javascript", "typescript"]
// Iterate over all parameters
for (const [key, value] of params) {
console.log(`${key}: ${value}`);
}
// Convert to object (loses duplicate keys)
const obj = Object.fromEntries(params);Note that get() always returns a string or null—never a number, even for numeric values. The getAll() method is essential when the same key can appear multiple times (like checkboxes or multi-select filters). Converting to an object with Object.fromEntries() is convenient but loses duplicate keys, so use it carefully.
Adding and Modifying Parameters
const url = new URL('https://example.com/products');
// Add parameters
url.searchParams.set('category', 'shoes');
url.searchParams.set('sort', 'price');
// set() replaces existing value
url.searchParams.set('sort', 'name'); // Now sorted by name
// append() adds another value (for arrays)
url.searchParams.append('color', 'red');
url.searchParams.append('color', 'blue');
// Result: ?category=shoes&sort=name&color=red&color=blue
// Delete a parameter
url.searchParams.delete('sort');
// Delete all values for a key
url.searchParams.delete('color');
// Get the final URL
console.log(url.toString());
// "https://example.com/products?category=shoes"The distinction between set() and append() is crucial: set() replaces any existing value, while append() adds another value for the same key. Use set() for single-value parameters and append() for array-like data.
Building Query Strings from Scratch
// From an object
const filters = {
category: 'electronics',
minPrice: 100,
maxPrice: 500,
inStock: true
};
const params = new URLSearchParams(filters);
console.log(params.toString());
// "category=electronics&minPrice=100&maxPrice=500&inStock=true"
// From arrays (each value becomes separate parameter)
const tags = ['javascript', 'typescript', 'react'];
const params = new URLSearchParams();
tags.forEach(tag => params.append('tag', tag));
console.log(params.toString());
// "tag=javascript&tag=typescript&tag=react"
// Building complete URL
const baseUrl = 'https://api.example.com/search';
const url = new URL(baseUrl);
url.searchParams.set('q', 'laptop');
url.searchParams.set('sort', 'rating');
console.log(url.href);
// "https://api.example.com/search?q=laptop&sort=rating"You can initialize URLSearchParams with an object for quick conversion, but note that object values must be strings (or convertible to strings). For arrays, use append() in a loop as shown. The final pattern—combining new URL() with searchParams—is the cleanest way to build complete URLs.
JavaScript isn't the only game in town. Let's see how other popular languages handle query parameters.
Query Parameters in Other Languages
Every backend language has its own API for query string handling. This section provides idiomatic examples in Python, PHP, Go, and Ruby—copy these patterns directly into your projects.
from urllib.parse import parse_qs, urlencode, urlparse
# Parse query string from URL
url = "https://example.com/search?q=hello&tags=js&tags=py"
parsed = urlparse(url)
params = parse_qs(parsed.query)
# {'q': ['hello'], 'tags': ['js', 'py']}
# Note: parse_qs returns lists for all values
# Get single value
query = params.get('q', [''])[0] # "hello"
# Build query string from dict
params = {'q': 'laptop', 'sort': 'price', 'page': 1}
query_string = urlencode(params)
# "q=laptop&sort=price&page=1"
# Handle multiple values for same key
from urllib.parse import urlencode
params = [('tag', 'js'), ('tag', 'py'), ('tag', 'go')]
query_string = urlencode(params)
# "tag=js&tag=py&tag=go"Notice how each language returns lists for values (Python, Ruby) or requires explicit handling of multiple values (Go, PHP). This is because query strings can have repeated keys, so the APIs are designed to handle that case. When you know a key is single-valued, grab the first element.
All these languages handle encoding automatically, but let's understand what's happening behind the scenes.
Encoding Special Characters
Certain characters have special meaning in URLs and must be encoded when they appear in parameter values. This section covers the most important characters to watch for and how URLSearchParams handles them automatically.
Query parameter values often contain characters that have special meaning in URLs. These must be percent-encoded to work correctly.
The table below lists characters that require encoding and why. When you see these in user input, they need special handling.
| Character | Encoded | Why it needs encoding |
|---|---|---|
| Space | %20 | Not allowed in URLs |
| & | %26 | Separates parameters |
| = | %3D | Separates key from value |
| ? | %3F | Starts query string |
| # | %23 | Starts fragment |
| % | %25 | Encoding character itself |
| + | %2B | Often decoded as space |
The good news: URLSearchParams handles encoding automatically:
const params = new URLSearchParams();
// Special characters are encoded automatically
params.set('query', 'Tom & Jerry');
params.set('equation', '1+1=2');
params.set('url', 'https://example.com?foo=bar');
console.log(params.toString());
// "query=Tom+%26+Jerry&equation=1%2B1%3D2&url=https%3A%2F%2Fexample.com%3Ffoo%3Dbar"
// And decoded automatically when reading
const decoded = params.get('query'); // "Tom & Jerry"The key insight: you work with plain strings and the API handles all encoding/decoding. The ampersand in "Tom & Jerry" becomes %26 in the URL, the plus sign in "1+1=2" becomes %2B, and the nested URL gets fully encoded. When you read values back, you get the original strings—no manual decoding needed.
Avoid manual string concatenation
Building URLs with url + '?q=' + query breaks when query contains special characters. Always use URLSearchParams or equivalent.
Encoding is essential, but what about complex data like arrays? That's where things get interesting.
Passing Arrays and Objects
Query strings are fundamentally flat—there's no native array or object syntax. This section shows the four common conventions for passing complex data and when to use each one.
URLs are strings, so there's no native way to pass arrays or objects. Different frameworks use different conventions:
Repeated Keys (Most Common)
// Most servers and frameworks support this
?color=red&color=blue&color=green
// JavaScript
const params = new URLSearchParams();
['red', 'blue', 'green'].forEach(c => params.append('color', c));
// Reading back
const colors = params.getAll('color'); // ['red', 'blue', 'green']Repeated keys are the most widely supported convention. Express, Django, Spring, and most other frameworks handle this natively. The key appears multiple times, and the server collects all values into an array.
Bracket Notation (PHP, Rails)
// PHP and Rails style
?colors[]=red&colors[]=blue&colors[]=green
// JavaScript (manual)
const colors = ['red', 'blue', 'green'];
const params = colors.map((c, i) => `colors[]=${encodeURIComponent(c)}`).join('&');
// Indexed arrays
?colors[0]=red&colors[1]=blue&colors[2]=green
// Nested objects
?filter[status]=active&filter[type]=premiumBracket notation is the preferred style in PHP and Ruby on Rails. The brackets tell the server to parse the values as an array or nested object. Note that the brackets themselves get encoded (%5B and %5D), but servers understand this encoding.
Comma-Separated (Simple APIs)
// Some APIs use comma-separated values
?colors=red,blue,green
// Building
const colors = ['red', 'blue', 'green'];
const params = new URLSearchParams();
params.set('colors', colors.join(','));
// Parsing (server-side)
const colors = params.get('colors').split(',');Comma-separated values are simple and produce short URLs. However, they break if your values contain commas, so this approach works best for controlled vocabularies like color names or category IDs—not user-generated content.
JSON (Complex Data)
// For complex nested data, some APIs accept JSON
const filters = {
status: ['active', 'pending'],
price: { min: 10, max: 100 },
tags: ['sale', 'featured']
};
const params = new URLSearchParams();
params.set('filters', JSON.stringify(filters));
// Result: ?filters=%7B%22status%22%3A%5B%22active%22...
// (heavily encoded - use with caution due to length)JSON in query strings handles any data structure but creates very long URLs due to encoding. Use this sparingly—URLs over 2,000 characters can cause problems. For complex filter states, consider POST requests or storing state server-side with a shorter reference ID.
With the mechanics covered, let's look at real-world patterns for common use cases.
Common Use Cases
Query parameters appear in nearly every web application. This section provides patterns for the most common scenarios: pagination, filtering, search, and analytics tracking. Use these as starting points for your own implementations.
Pagination
// Standard pagination parameters
?page=2&limit=20
?page=2&per_page=20
?offset=20&limit=20
// Cursor-based pagination
?cursor=eyJpZCI6MTAwfQ
// Example: Building pagination links
function getPaginationUrl(page, limit) {
const url = new URL(window.location.href);
url.searchParams.set('page', page);
url.searchParams.set('limit', limit);
return url.toString();
}Offset-based pagination (page + limit) is simple but has performance issues at scale. Cursor-based pagination is more efficient for large datasets—the cursor is typically an encoded ID or timestamp that lets the database seek directly to the right position.
Filtering and Sorting
// Filtering
?category=electronics&brand=apple&minPrice=500&maxPrice=1000&inStock=true
// Sorting
?sort=price&order=asc
?sort=price:asc
?sort=-price // Minus prefix for descending
?sortBy=price&sortDir=desc
// Combined filtering UI example
function buildFilterUrl(filters) {
const url = new URL('/products', window.location.origin);
if (filters.category) url.searchParams.set('category', filters.category);
if (filters.minPrice) url.searchParams.set('minPrice', filters.minPrice);
if (filters.maxPrice) url.searchParams.set('maxPrice', filters.maxPrice);
if (filters.sort) url.searchParams.set('sort', filters.sort);
return url.toString();
}The filter URL builder pattern is useful for search pages: only include parameters that have values, keeping URLs clean. The sorting conventions vary—sort=price:asc, sort=-price (minus for descending), or separate sortBy and sortDir parameters are all common approaches.
Search
// Simple search
?q=laptop
// Advanced search with filters
?q=laptop&category=electronics&minRating=4&freeShipping=true
// Search with facets
?q=laptop&facets=brand,price_range,rating
// Autocomplete
?q=lap&type=suggest&limit=5The query parameter q is nearly universal for search terms—users expect it, and it's easy to remember. Autocomplete endpoints typically use the same parameter with additional options to limit results and specify the response type.
Analytics and Tracking
// UTM parameters (marketing)
?utm_source=newsletter&utm_medium=email&utm_campaign=summer_sale
// Referral tracking
?ref=friend123&source=share
// A/B testing
?variant=B&experiment=checkout_flow_v2
// Session/user tracking (use with privacy considerations)
?session_id=abc123&user_id=user456UTM parameters (utm_source, utm_medium, utm_campaign) are the standard for marketing attribution in Google Analytics. For more details on tracking URLs, see our UTM Parameters guide.
With common patterns covered, let's discuss best practices that apply across all use cases.
Best Practices
Following these guidelines will help you build URLs that are readable, maintainable, and secure. These aren't just style preferences—they prevent real bugs and security vulnerabilities.
Use semantic parameter names
?category=shoes is better than ?c=s. Clear names make URLs readable and debuggable.
Keep URLs under 2,000 characters
Some browsers and servers have lower limits. If you need more data, use POST with a request body.
Use lowercase parameter names
While URLs are case-sensitive, lowercase is conventional and avoids confusion. Use page not Page.
Validate on the server
Never trust query parameters. Validate types, ranges, and allowed values server-side. ?page=-1 shouldn't crash your app.
Don't put sensitive data in URLs
URLs appear in browser history, server logs, and referrer headers. Never include passwords, API keys, or tokens in query strings.
Avoid changing state with GET
URLs with query parameters should be safe to reload or share. ?action=delete is dangerous—use POST for state changes.
Security deserves special attention when working with query parameters. User-controlled input in URLs can be a vector for attacks if not handled properly.
Security Considerations
Query parameters are user-controlled input—treat them with the same caution as form data. This section covers XSS prevention and validation patterns that keep your application secure.
Preventing XSS
// DANGEROUS: Directly inserting parameter value
const query = params.get('search');
document.innerHTML = `Results for: ${query}`; // XSS vulnerability!
// SAFE: Use textContent for text display
const query = params.get('search');
document.querySelector('.search-term').textContent = query;
// SAFE: Escape HTML if you must use innerHTML
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
document.innerHTML = `Results for: ${escapeHtml(query)}`;The rule is simple: never use innerHTML with user input. If an attacker crafts a URL like ?search=<script>alert('XSS')</script> and tricks a user into clicking it, the script executes in that user's browser. Use textContent for text display, or escape HTML if you must use innerHTML.
Parameter Validation
// Validate and sanitize server-side
function validatePagination(params) {
const page = parseInt(params.get('page'), 10);
const limit = parseInt(params.get('limit'), 10);
return {
page: Number.isInteger(page) && page > 0 ? page : 1,
limit: [10, 20, 50, 100].includes(limit) ? limit : 20
};
}
// Whitelist allowed values
const ALLOWED_SORT = ['price', 'name', 'date', 'rating'];
const sort = params.get('sort');
if (!ALLOWED_SORT.includes(sort)) {
throw new Error('Invalid sort parameter');
}Validation code shows two patterns: safe defaults (invalid page becomes 1, invalid limit becomes 20) and strict whitelisting (invalid sort throws an error). Which pattern to use depends on context—for pagination, graceful defaults are fine; for security-sensitive parameters, fail fast with an error.
Query parameters don't just affect functionality—they can impact your search engine rankings too.
SEO and Query Parameters
Search engines can treat each unique URL as a separate page, which creates duplicate content issues when query parameters don't change the core content. This section covers canonical URLs and Google Search Console configuration.
Query parameters can create SEO challenges. The same content might be accessible via multiple URLs:
# These might all show the same products
/products?category=shoes
/products?category=shoes&sort=price
/products?sort=price&category=shoes
/products?category=shoes&page=1Use Canonical URLs
<!-- Tell search engines the preferred URL -->
<link rel="canonical" href="https://example.com/products?category=shoes" />
<!-- Ignore tracking parameters -->
<!-- If current URL is: /products?category=shoes&utm_source=google -->
<link rel="canonical" href="https://example.com/products?category=shoes" />The canonical tag tells search engines which URL to index when multiple URLs show similar content. Strip tracking parameters from canonical URLs—Google doesn't need to index your UTM variants separately.
Configure in Google Search Console
Google Search Console lets you tell Google how to handle specific parameters:
- utm_* — Ignore (doesn't change content)
- sort — Representative URL (same content, different order)
- page — Every URL (different content)
- category — Every URL (different content)