DEV Community

Snappy Tools
Snappy Tools

Posted on • Originally published at snappytools.app

URL Encoding Explained: Why Special Characters Break Your URLs (and How to Fix It)

You paste a URL into your browser and it works fine. You programmatically construct the same URL in code, add a parameter value with a space or ampersand in it, and suddenly the request breaks or returns unexpected results.

URL encoding — also called percent encoding — is why. Here's what it is, when you need it, and how to apply it correctly.

What Is URL Encoding?

URLs can only contain a limited set of characters defined in RFC 3986: letters (A–Z, a–z), digits (0–9), and a handful of special characters (-, _, ., ~). Everything else must be encoded.

Encoding works by replacing the character with a % followed by its two-digit hexadecimal ASCII code.

Character Encoded
Space %20
& %26
= %3D
+ %2B
/ %2F
? %3F
# %23
@ %40

So a URL like:

https://example.com/search?q=hello world&sort=date+desc
Enter fullscreen mode Exit fullscreen mode

becomes:

https://example.com/search?q=hello%20world&sort=date%2Bdesc
Enter fullscreen mode Exit fullscreen mode

Reserved vs Unreserved Characters

The tricky part: some characters have special meaning in the URL structure itself and some are just general-purpose.

Reserved characters (!, *, ', (, ), ;, :, @, &, =, +, $, ,, /, ?, #, [, ]) — they can appear in a URL unencoded when used for their structural purpose (e.g., / separates path segments, ? starts the query string). When they appear as data inside a parameter value, they must be encoded.

Unreserved characters (A–Z, a–z, 0–9, -, _, ., ~) — these never need encoding.

This is the source of most bugs: a / in a path segment is fine. A / inside a query parameter value must be %2F.

The + vs %20 Trap

There are two URL encoding specifications in common use:

RFC 3986 (standard URL encoding): encodes space as %20. Use this for full URLs, path segments, and modern API requests.

application/x-www-form-urlencoded (HTML form encoding): encodes space as +. This is what HTML forms and jQuery's $.ajax() use by default.

Mixing them causes bugs. If your backend expects form encoding and you send %20, the %20 gets through fine (most decoders handle both). But if you encode a literal + as + when you mean a plus sign, the decoder interprets it as a space.

The safest rule: use %20 for spaces in all contexts except traditional HTML form submissions. Never rely on + in API parameters.

How to Encode URLs in Code

JavaScript:

// Encode a full URL component (query parameter value)
const query = encodeURIComponent('hello world & more');
// → 'hello%20world%20%26%20more'

// Encode a full URL while preserving structural characters (/, ?, &, =)
const url = encodeURI('https://example.com/search?q=hello world');
// → 'https://example.com/search?q=hello%20world'
Enter fullscreen mode Exit fullscreen mode

Key difference: encodeURIComponent encodes everything including /, ?, & — use for parameter values. encodeURI preserves structural characters — use when encoding a complete URL.

Python:

from urllib.parse import quote, quote_plus, urlencode

# Encode a path segment (preserves /)
quote('/path/to/resource')  # → '/path/to/resource'

# Encode a query parameter value
quote('hello world & more')  # → 'hello%20world%20%26%20more'

# HTML form encoding (spaces as +)
quote_plus('hello world')  # → 'hello+world'

# Build a query string from a dict
urlencode({'q': 'hello world', 'page': 1})
# → 'q=hello+world&page=1'  (uses form encoding by default)

# For RFC 3986 encoding in urlencode:
urlencode({'q': 'hello world'}, quote_via=quote)
# → 'q=hello%20world'
Enter fullscreen mode Exit fullscreen mode

PHP:

// Encode a query parameter value (RFC 3986)
rawurlencode('hello world & more');
// → 'hello%20world%20%26%20more'

// Build a query string
http_build_query(['q' => 'hello world', 'page' => 1]);
// → 'q=hello+world&page=1'  (uses form encoding)
Enter fullscreen mode Exit fullscreen mode

Decoding URLs

JavaScript:

decodeURIComponent('hello%20world%20%26%20more');
// → 'hello world & more'

decodeURI('https://example.com/search?q=hello%20world');
// → 'https://example.com/search?q=hello world'
Enter fullscreen mode Exit fullscreen mode

Python:

from urllib.parse import unquote, unquote_plus

unquote('hello%20world%20%26%20more')
# → 'hello world & more'

unquote_plus('hello+world')
# → 'hello world'
Enter fullscreen mode Exit fullscreen mode

Common Mistakes

Double encoding: If you encode a URL and then encode it again, %20 becomes %2520 (% gets encoded to %25). Always encode once, at the point of construction — not at the point of use.

Encoding structural characters: If you encode the ? or & in ?q=hello&page=1, the query string breaks entirely because the parser no longer sees the structural delimiters. Encode only the values, not the URL skeleton.

Not encoding at all: Constructing URLs by string concatenation without encoding is fragile. The moment a user types a &, =, or # in a search field, it breaks the URL.

Forgetting the fragment: The URL fragment (#section) is never sent to the server — it's client-side only. Don't encode it with the rest of the URL if you're building requests.

Quick Reference: When to Encode What

Scenario Use
Full URL going into a browser encodeURI()
Query parameter value encodeURIComponent()
HTML form submission Browser handles it
API request parameter encodeURIComponent() / quote()
Path segment with special chars encodeURIComponent() / quote()

Need to encode or decode a URL right now? SnappyTools URL Encoder/Decoder handles both RFC 3986 and form encoding in your browser — no install needed.

Top comments (0)