Certain characters have special meaning in URL query strings. When you want to use them as data (not as delimiters), they must be percent-encoded.
Key Takeaways
- 1& separates parameters—encode as %26 in values
- 2= separates keys from values—encode as %3D in values
- 3+ represents a space in query strings—encode as %2B for literal +
- 4# ends the query and starts the fragment—always encode
- 5Use URL API or encodeURIComponent() to handle encoding automatically
“URIs include components and subcomponents that are delimited by characters in the reserved set. If data for a URI component would conflict with a reserved character's purpose as a delimiter, then the conflicting data must be percent-encoded before the URI is formed.”— RFC 3986, Section 2.2
Characters That Must Be Encoded
When you include these characters in parameter values, they must be percent-encoded to prevent them from being interpreted as URL syntax. The table below shows the most common offenders and why they cause problems when left unencoded.
| Character | Encoded | Why |
|---|---|---|
| & | %26 | Separates key-value pairs |
| = | %3D | Separates key from value |
| + | %2B | + means space in query strings |
| # | %23 | Starts fragment (ends query) |
| % | %25 | Encoding escape character |
| ? | %3F | Starts query string |
Missing just one of these encodings can silently break your URLs. Let's look at common scenarios where developers run into trouble and how to fix them.
Real-World Examples
These aren't hypothetical edge cases—they're problems that happen in production every day. Search queries, filter expressions, and programming language names frequently contain reserved characters that need careful handling.
// Problem: Search for "Tom & Jerry"
const broken = '?q=Tom & Jerry';
// Server sees: q=Tom, Jerry= (broken!)
// Solution: Encode the &
const url = new URL('https://example.com/search');
url.searchParams.set('q', 'Tom & Jerry');
// "?q=Tom+%26+Jerry" (correct!)
// Problem: Filter by equation "x=5"
const broken = '?filter=x=5';
// Server sees: filter=x, 5= (broken!)
// Solution: Encode the =
url.searchParams.set('filter', 'x=5');
// "?filter=x%3D5" (correct!)
// Problem: Search for "C++"
const broken = '?lang=C++';
// Server sees: lang=C (++ became spaces!)
// Solution: Encode the +
url.searchParams.set('lang', 'C++');
// "?lang=C%2B%2B" (correct!)Each example shows the same pattern: string concatenation leads to broken URLs, while the URL API handles encoding correctly. The URLSearchParams.set() method knows which characters need encoding and applies the right transformation automatically. Always prefer the URL API over manual string building.
Fortunately, not every character needs encoding. Understanding which characters are safe can make your URLs more readable while keeping them valid.
Safe Characters (No Encoding Needed)
The unreserved character set can appear in URLs without encoding. These characters have no special meaning in URL syntax, so you can use them freely in parameter names and values.
Alphanumeric: A-Z a-z 0-9
Unreserved: - _ . ~
These can appear unencoded in parameter values:
?name=John_Doe
?file=report-2024.pdf
?id=abc123Sticking to these characters when designing your URL structure keeps URLs clean and avoids encoding overhead. That said, spaces deserve special attention because they can be represented two different ways.
Space Handling
Spaces in URLs are a special case with two valid representations. Which one you see depends on how the URL was constructed and what tool generated it.
| Representation | When Used | Example |
|---|---|---|
| + | Form encoding (application/x-www-form-urlencoded) | q=hello+world |
| %20 | General percent-encoding | q=hello%20world |
The plus sign for spaces comes from the application/x-www-form-urlencoded format used by HTML forms. General percent-encoding uses %20. Both are decoded to a space by servers, so you don't need to worry about which format your tools produce.
// URLSearchParams uses + for spaces
const url = new URL('https://example.com');
url.searchParams.set('q', 'hello world');
url.href; // "https://example.com/?q=hello+world"
// encodeURIComponent uses %20
encodeURIComponent('hello world'); // "hello%20world"
// Both are valid and equivalent for query strings
// Servers decode both the same wayThe difference matters only if you need to produce a specific format (for example, when computing signatures for APIs like OAuth). In most cases, let your tools choose the representation and don't overthink it.
Automatic Encoding
The good news is that you rarely need to think about encoding if you use modern JavaScript APIs. They handle all the edge cases we've discussed automatically, so you can focus on your application logic instead of URL syntax.
// ✅ URL API encodes automatically
const url = new URL('https://api.example.com/search');
url.searchParams.set('q', 'Tom & Jerry');
url.searchParams.set('equation', '1+1=2');
url.searchParams.set('tag', '#trending');
// All special characters are encoded correctly
console.log(url.href);
// "https://api.example.com/search?q=Tom+%26+Jerry&equation=1%2B1%3D2&tag=%23trending"
// ❌ String concatenation requires manual encoding
const broken = 'https://api.example.com/search?q=' + 'Tom & Jerry';
// Broken: & not encodedThe rule is simple: always use the URL API or encodeURIComponent() when building URLs with dynamic values. String concatenation might work for simple cases, but it will eventually bite you when someone searches for “C++” or “salt & pepper.”