← Back to Blog
Web DevelopmentURLsEncoding

URL Encoding Explained: When and Why to Encode URLs

Paste https://example.com/search?q=hello world into a browser and it quietly works. Paste it into a fetch call or an API parameter and things break — sometimes silently.

That's percent-encoding at work (or not). This post explains what URL encoding is, when you must do it, when you must not, and the specific JavaScript gotcha that catches most developers at least once.

If you want to follow along, open the URL Encoder in another tab.

What URL encoding actually is

A URL has a defined character set: letters, digits, and a small handful of special characters (-, _, ., ~). Everything else — spaces, ampersands, slashes in unexpected places, non-ASCII characters — needs to be encoded.

Percent-encoding replaces each byte with % followed by two hex digits representing that byte. A space becomes %20. The @ sign becomes %40. The euro sign (a multi-byte UTF-8 character) becomes %E2%82%AC.

The spec for this is RFC 3986, the URI standard. The short version: a URL is a sequence of ASCII characters, and any character that doesn't belong in a specific part of the URL must be escaped.

The two JavaScript functions and why you need both

JavaScript has two native encoding functions. Most developers use one when they need the other.

encodeURIComponent

Encodes everything except:

  • Unreserved characters: A–Z a–z 0–9 - _ . ~

Use this for values you're inserting into a URL — query string parameters, path segments derived from user input.

const query = "coffee & cake";
const url = `https://example.com/search?q=${encodeURIComponent(query)}`;
// → https://example.com/search?q=coffee%20%26%20cake

The & is critical here. Without encoding, the browser interprets it as a query parameter separator and the request breaks in half.

encodeURI

Encodes everything encodeURIComponent does, but leaves intact the characters that have structural meaning in a URL: : / ? # [ ] @ ! $ & ' ( ) * + , ; =

Use this only when you have a complete URL and you want to encode any stray characters without destroying its structure.

const url = encodeURI("https://example.com/my path/to/resource?q=hello world");
// → https://example.com/my%20path/to/resource?q=hello%20world

The spaces get encoded. The :, /, ?, = stay as-is.

The trap: using encodeURI on a value

If you use encodeURI on a query parameter value:

const tag = "coffee&cake";
const url = `https://example.com?tag=${encodeURI(tag)}`;
// → https://example.com?tag=coffee&cake  ← WRONG

The & doesn't get encoded because encodeURI preserves it. Your server now sees tag=coffee and an extra parameter cake with no value.

Rule of thumb: Use encodeURIComponent for values. Use encodeURI only for whole URLs.

Python and shell: quick reference

Python (urllib.parse)

from urllib.parse import quote, urlencode

# encode a single value
value = "café & tea"
encoded = quote(value, safe="")  # → "caf%C3%A9%20%26%20tea"

# build a full query string
params = {"q": "hello world", "lang": "en"}
qs = urlencode(params)  # → "q=hello+world&lang=en"

Note: urlencode uses + for spaces by default (application/x-www-form-urlencoded format), not %20. More on that in a moment.

curl

# --data-urlencode handles encoding for you in POST bodies
curl -G "https://example.com/search" \
  --data-urlencode "q=hello world" \
  --data-urlencode "tag=coffee & cake"

For GET requests, --data-urlencode with -G appends properly encoded query parameters. You don't need to pre-encode anything.

The + vs %20 confusion

You'll see both %20 and + used to represent a space in URLs. They're not interchangeable.

  • %20 is the RFC 3986 percent-encoding for a literal space character. Valid in all URL contexts.
  • + means space only in the application/x-www-form-urlencoded content type — the format HTML forms use when submitted via GET or POST.

In a query string inside an HTML form, q=hello+world and q=hello%20world mean the same thing to a server that's parsing form data. But in a plain URL (like a REST API endpoint), a + in the query string might be treated as a literal + character, not a space, depending on the server.

The Web Standard (WHATWG URL spec) actually diverges from RFC 3986 on this point for historical reasons. The safe default:

  • Building URLs programmatically: use encodeURIComponent (produces %20, not +).
  • Submitting HTML forms: let the browser handle it; it will use + correctly.
  • Using URLSearchParams: it also uses + for spaces. If you paste a URLSearchParams string into a context that doesn't parse it as form data, %20 is safer.

Try the difference in the URL Encoder: encode hello world and see hello%20world vs what URLSearchParams produces.

When NOT to encode

This catches people as often as forgetting to encode.

Don't encode a URL you didn't build

If you receive a URL from an external source and want to pass it along, don't run encodeURIComponent on the whole thing. https://example.com/path?q=hello%20world will become https%3A%2F%2Fexample.com%2Fpath%3Fq%3Dhello%2520world — double-encoded, completely broken.

Only encode values you're inserting into a URL.

Don't encode the structural parts

Encoding the path separator breaks routing. Encoding the ? or & makes query strings unreadable to the server.

// WRONG — encodes the slashes
const path = encodeURIComponent("/api/v1/users");
// → %2Fapi%2Fv1%2Fusers

// RIGHT — only encode the user-supplied segment
const userId = "user/42"; // unusual, but possible
const path = `/api/v1/users/${encodeURIComponent(userId)}`;
// → /api/v1/users/user%2F42

Edge cases worth knowing

Double-encoding

The most common URL encoding bug in production. You encode a value, store it, then encode it again when constructing the next URL. The receiving server sees %2520 instead of %20 and can't decode it correctly.

The fix: decode before encoding, or be explicit about which layer owns the encoding step.

Path segments need encoding too

Most developers remember to encode query parameters. They forget that user-supplied data in path segments can also contain unsafe characters.

// File name from user input: "my report (final).pdf"
const safePath = `/files/${encodeURIComponent(filename)}`;
// → /files/my%20report%20(final).pdf

Path encoding with encodeURIComponent is slightly more aggressive than necessary — the ( and ) are technically valid in paths per RFC 3986 — but it's the safe default.

International domain names (IDN)

Domain names with non-ASCII characters (like münchen.de) are represented in URLs using Punycode — a different encoding scheme, not percent-encoding. The browser handles this for you in the address bar, but if you're constructing URLs in code that include international domain names, use a URL parsing library rather than string concatenation.

For the encoding of path and query components with non-ASCII characters, percent-encoding with UTF-8 bytes is the right approach and what encodeURIComponent does.

The # fragment and encoding

The hash/fragment (#) signals the end of the URL to most parsers. If you have a # in a query value, encodeURIComponent will encode it correctly to %23. If you're using encodeURI, it won't — # is preserved as a structural character, which will break your URL.

This is another reason encodeURIComponent should be your default for values.

How this connects to other encodings

URL encoding is one of several encoding schemes you'll encounter as a web developer. It's easy to mix them up.

Base64 encoding is for encoding binary data as ASCII text — used in data URIs, basic auth headers, and email attachments. Not for URLs, though base64 strings sometimes appear as URL parameter values (and then need to be percent-encoded if they contain +, /, or =).

HTML entity encoding (&, <) is for embedding characters in HTML without confusing the parser. Separate problem, separate solution.

Hashing transforms data irreversibly for integrity checks — nothing to do with encoding/decoding. But you might hash a URL for caching or deduplication purposes.

FAQ

What's the difference between URL encoding and HTML encoding?

URL encoding (percent-encoding) makes characters safe for use in URLs. HTML encoding (entity encoding) makes characters safe for embedding in HTML markup. An & in a URL becomes %26; in HTML it becomes &. They solve different problems and you should not mix them up.

Should I encode the whole URL or just the values?

Just the values. Encoding the whole URL destroys its structure. The colon after https, the slashes, the ?, the & separating parameters — all of those need to stay as-is for the URL to work.

Why does my URL have %20 sometimes and + sometimes?

Both represent a space, but in different contexts. %20 is the RFC-compliant percent-encoding. + means space only in application/x-www-form-urlencoded format (what HTML forms submit). Build URLs programmatically with encodeURIComponent to always get %20.

What if I just need to check what a specific character encodes to?

The URL Encoder tool runs entirely in your browser — paste in a string and see the encoded result instantly. No data leaves your browser.

Does the path segment need encoding too?

Yes. Any user-supplied data that ends up in a path segment (not just query parameters) needs to be encoded. A filename like my report (2026).pdf will break routing if you drop it in a path unencoded. Use encodeURIComponent for path segments from user input.

Quick reference

Situation Use
Inserting a value into a query string encodeURIComponent(value)
Sanitizing a complete URL you already have encodeURI(url)
Building a query string in JS new URLSearchParams(params).toString()
Form data (HTML form submission) Browser handles it
Python query string urllib.parse.urlencode(params)
Python single value urllib.parse.quote(value, safe="")
curl --data-urlencode "key=value"

When in doubt, encode the values and leave the structure alone. The URL Encoder is there when you need to double-check an edge case.

Sources

Try the tool mentioned in this article:

Open Tool →