Encoding

URL Encoding Special Characters: The Complete Guide (2026)

2026-05-18 · 9 min read · Encode URLs free →

You paste a URL into your browser and it works. You add a space or an ampersand and suddenly the request fails, parameters get dropped, or you get a 400 error. URL encoding — also called percent-encoding — is the mechanism that makes arbitrary text safe to include in URLs. Understanding it precisely saves hours of debugging API calls, query strings, and form submissions. This guide explains every aspect of URL encoding with real examples and copy-paste code.

Why URLs Need Encoding

URLs can only contain a specific set of ASCII characters. The characters that are always safe in any URL component are the unreserved characters: uppercase and lowercase letters A–Z and a–z, digits 0–9, and four symbols: hyphen -, underscore _, period ., and tilde ~.

Every other character — spaces, ampersands, equals signs, slashes, question marks, hash symbols, non-ASCII characters, Unicode — must be encoded before it can safely appear in a URL. The encoding format is a percent sign followed by two hexadecimal digits representing the byte value: a space becomes %20, an ampersand becomes %26, a euro sign becomes %E2%82%AC (three bytes in UTF-8).

The Most Common Percent-Encoded Characters

CharacterEncodedWhere it appears
Space%20 (or + in forms)Search queries, names with spaces
&%26Parameter values containing &
=%3DParameter values containing =
+%2BLiteral plus sign in query values
/%2FSlashes in path segments or values
?%3FQuestion marks in parameter values
#%23Hash symbols in parameter values
@%40At signs in URLs (emails in params)
:%3AColons in parameter values
%%25Literal percent signs
"%22Quote characters
<%3CHTML angle brackets
>%3EHTML angle brackets

URL Components and What Needs Encoding

A URL has multiple components, and the encoding rules differ between them. The structure is: scheme://userinfo@host:port/path?query#fragment. Slashes in a path segment are significant — they separate path components. A slash that is part of a value in a path segment must be encoded as %2F. Slashes that are path separators must not be encoded.

Query string encoding

This is where percent-encoding matters most for everyday API work. Query string values must encode: spaces, ampersands, equals signs, plus signs, and all non-ASCII characters. Query string keys follow the same rules.

// WRONG — raw special characters break the query string
https://api.example.com/search?q=hello world&filter=price>100

// RIGHT — values properly encoded
https://api.example.com/search?q=hello%20world&filter=price%3E100

The + vs %20 distinction

In HTML form submissions (application/x-www-form-urlencoded), spaces are encoded as +, not %20. This is a legacy convention from early HTML. In modern URL encoding outside of form contexts, %20 is correct and unambiguous. The confusion: a literal plus sign must be encoded as %2B in form data. If your API is receiving + and treating it as a space when you intended a literal plus, this is why.

Rule of thumb: Use encodeURIComponent() in JavaScript (which produces %20 for spaces) for API calls. Use URLSearchParams for building query strings — it handles all encoding correctly.

Encoding in JavaScript

JavaScript has three encoding functions, each with a different scope. Choosing the wrong one is one of the most common JavaScript bugs.

// encodeURI() — encodes a complete URL, leaves structural characters alone
encodeURI('https://example.com/path with spaces?q=hello world');
// "https://example.com/path%20with%20spaces?q=hello%20world"
// Does NOT encode: : / ? # [ ] @ ! $ & ' ( ) * + , ; =

// encodeURIComponent() — encodes a single value/component
encodeURIComponent('hello & world = great!');
// "hello%20%26%20world%20%3D%20great!"
// Encodes everything except: A-Z a-z 0-9 - _ . ! ~ * ' ( )

// decodeURIComponent() — decodes a percent-encoded component
decodeURIComponent('hello%20%26%20world');
// "hello & world"

// URLSearchParams — the RIGHT way to build query strings
const params = new URLSearchParams({
  q: 'hello world',
  filter: 'price>100',
  tag: 'C++ programming',
});
const url = `https://api.example.com/search?${params}`;
// "https://api.example.com/search?q=hello+world&filter=price%3E100&tag=C%2B%2B+programming"

// Fetching with encoded parameters — correct pattern
const url = new URL('https://api.example.com/search');
url.searchParams.set('q', userInput);       // handles encoding automatically
url.searchParams.set('page', pageNumber);
const response = await fetch(url);

Common mistake: double encoding

// WRONG — encoding an already-encoded string
const encoded = encodeURIComponent('hello world'); // "hello%20world"
encodeURIComponent(encoded);                        // "hello%2520world"  ← %25 = encoded %

// ALWAYS decode before re-encoding if the string might already be encoded
const safe = encodeURIComponent(decodeURIComponent(maybeEncoded));

Encoding in Python

from urllib.parse import quote, quote_plus, urlencode, urljoin

# quote() — encodes path segments (leaves / unencoded)
quote('/search/hello world')
# '/search/hello%20world'

# quote() with safe='' — encodes everything including /
quote('hello/world & more', safe='')
# 'hello%2Fworld%20%26%20more'

# quote_plus() — uses + for spaces (form encoding)
quote_plus('hello world & more')
# 'hello+world+%26+more'

# urlencode() — encodes a dict into query string
urlencode({'q': 'hello world', 'filter': 'price>100', 'tag': 'C++'})
# 'q=hello+world&filter=price%3E100&tag=C%2B%2B'

# Full URL construction
from urllib.parse import urlunparse, urlencode
query = urlencode({'q': 'search term', 'limit': 10})
url = urlunparse(('https', 'api.example.com', '/search', '', query, ''))
# "https://api.example.com/search?q=search+term&limit=10"

Encoding Non-ASCII and Unicode Characters

Non-ASCII characters (accented letters, Chinese characters, emoji) are first encoded to UTF-8 bytes, then each byte is percent-encoded. The euro sign € in UTF-8 is three bytes: 0xE2, 0x82, 0xAC — so it encodes as %E2%82%AC. The emoji 🔑 is four UTF-8 bytes: 0xF0, 0x9F, 0x94, 0x91 — encoding to %F0%9F%94%91.

encodeURIComponent('€');   // "%E2%82%AC"
encodeURIComponent('🔑');  // "%F0%9F%94%91"
encodeURIComponent('José'); // "Jos%C3%A9"  (é = 0xC3 0xA9 in UTF-8)

Try encoding any text or URL with ToolPry's URL Encoder — it handles Unicode correctly and shows you both the encoded output and the individual byte values for non-ASCII characters.

Decoding Percent-Encoded URLs for Debugging

When you receive a URL in a log, an error message, or a webhook payload, it is often percent-encoded and difficult to read. Decoding it quickly reveals what the actual values are.

// JavaScript
decodeURIComponent('hello%20%26%20world%20%3D%20great');
// "hello & world = great"

// Python
from urllib.parse import unquote
unquote('hello%20%26%20world%20%3D%20great')
# "hello & world = great"

// Command line (Linux/Mac)
python3 -c "from urllib.parse import unquote; print(unquote('hello%20world'))"

Frequently Asked Questions

What is the difference between encodeURI and encodeURIComponent?

encodeURI is designed to encode a complete URL — it leaves characters that have structural meaning in URLs unencoded (: / ? # [ ] @ ! $ & ' ( ) * + , ; =). Use it when you have a complete URL that might contain spaces or non-ASCII but is otherwise valid. encodeURIComponent encodes everything except the unreserved characters — use it for individual parameter values, path segments, or any string that will be embedded inside a URL. In practice, you should almost always use encodeURIComponent for API work and use URLSearchParams or the URL constructor for building complete URLs.

Why do some URLs have %20 and others have + for spaces?

%20 is the RFC 3986 standard encoding for a space in a URL. + represents a space only in the application/x-www-form-urlencoded content type, which is what HTML forms submit by default. Both decode to a space in query string contexts, but they are not interchangeable everywhere. In path segments, + is a literal plus sign, not a space. When in doubt, use %20 — it is unambiguous in all URL contexts.

Do I need to encode forward slashes in path segments?

It depends on whether the slash is a path separator or part of a value. Path separators should not be encoded. A slash that is part of a value — for example, a filename containing a slash, or a base64url-encoded value — must be encoded as %2F to prevent it from being interpreted as a path separator. Many REST APIs use path parameters that might contain special characters; always encode parameter values with encodeURIComponent before interpolating them into URL paths.

How do I handle URL encoding in a REST API client?

Use your language's URL building library rather than string concatenation. In JavaScript, use the URL constructor with searchParams. In Python, use urllib.parse.urlencode and urllib.parse.urljoin. In Node.js with axios, pass parameters as the params option — it handles encoding automatically. Never build URLs by concatenating strings with user input; this causes both URL encoding bugs and potential security issues.

URL Encoding in HTTP APIs and Frameworks

Modern HTTP frameworks and API clients handle most URL encoding automatically, but understanding what they do prevents the bugs that occur when the automatic handling does not cover your specific case.

Axios (JavaScript): Pass parameters as the params option — Axios uses URLSearchParams internally and handles encoding. Be aware that Axios by default uses a custom serializer that represents arrays as key[]=val1&key[]=val2 — configure paramsSerializer if your API expects a different format.

Fetch API: Does not encode URLs automatically. Use the URL constructor with searchParams or URLSearchParams explicitly. Never concatenate user input directly into fetch URLs.

Requests (Python): Pass parameters as the params argument to requests.get(). The library handles encoding with urllib.parse.urlencode. Arrays are represented as repeated parameters by default.

Django: Uses django.utils.http.urlencode for building URLs. Template tag {% url %} handles path encoding automatically. For query strings in views, use urllib.parse.urlencode.

Express.js: req.query and req.params are automatically decoded. When building redirect URLs or response headers, use encodeURIComponent on user-provided values.

Security Implications of URL Encoding

Improper URL handling creates security vulnerabilities. Open redirect vulnerabilities occur when an application takes a URL from a query parameter and redirects the user to it. An attacker can encode a malicious URL to bypass naive validation: ?next=https%3A//evil.com. Always validate redirect targets against a whitelist of allowed domains after decoding.

Path traversal attacks use encoded path separators to escape the intended directory: /uploads/%2E%2E%2F%2E%2E%2Fetc%2Fpasswd. Web frameworks decode paths before routing, so percent-encoded traversal sequences reach your file system handler. Always resolve and validate file paths against the allowed base directory after decoding.

Double encoding attacks exploit applications that decode URLs multiple times. %2520 is a percent-encoded percent sign — decoded once it becomes %20, decoded twice it becomes a space. If your framework decodes and your application decodes again, an attacker can smuggle encoded characters through the first decode. Decode exactly once, at the application boundary.

Frequently Asked Questions

How do I encode a full URL that might already be partially encoded?

This is the trickiest URL encoding scenario. If you have a URL that might already be partially encoded, blindly encoding the whole string will double-encode existing percent signs. The safe approach: parse the URL with the URL constructor (JavaScript) or urllib.parse.urlparse (Python), which separates components correctly, then encode only the values that need encoding and reassemble. In JavaScript: new URL(possiblyEncodedUrl) normalises the URL without double-encoding it, then you can set searchParams to add or modify parameters.

Why do some API calls fail only when the parameter contains certain characters?

The characters that most commonly cause silent API failures are: & (terminates the current parameter and starts the next), = (separates key from value), + (decoded as space in form encoding), # (starts the fragment, everything after is not sent to the server), and % not followed by two hex digits (invalid percent sequence, may cause a parse error). Test your API calls with inputs containing &=+#% to find encoding gaps. Use ToolPry's URL Encoder to see exactly how any string encodes before sending it.