URL EncodingWeb DevelopmentHTTPAPIsDeveloper Tools

A Developer's Guide to URL Encoding and Percent-Encoding

By Hamid Abderrahim
A Developer's Guide to URL Encoding and Percent-Encoding

A Developer's Guide to URL Encoding and Percent-Encoding

If you have ever seen %20 appear where a space should be in a URL, or noticed %2F replacing a forward slash, you have encountered URL encoding — also called percent-encoding. It is one of the most fundamental concepts in web development, yet it is frequently misunderstood and misused, leading to broken links, failed API requests, and confusing bugs.

This guide explains exactly what URL encoding is, which characters require encoding and why, the critical differences between JavaScript's encoding functions, and how to handle edge cases like Unicode characters, form data, and query strings.

Why URL Encoding Exists

The URL specification (RFC 3986) defines a limited set of characters that are allowed in a URL without modification — letters A–Z, a–z, digits 0–9, and a handful of special characters (-, _, ., ~). Every other character must be percent-encoded before it can appear in a URL.

The reason is that URLs are transmitted as ASCII text through protocols and infrastructure that were designed before Unicode existed. Characters outside the safe set would be misinterpreted by servers, proxies, and browsers. The forward slash / already means "path separator." The ? separates the path from the query string. The & separates query parameters. If your data contains these characters, they must be encoded so the URL structure remains unambiguous.

Example:

A search for C++ programming & algorithms needs to become a URL-safe query string:

?q=C%2B%2B+programming+%26+algorithms

Or using %20 for spaces instead of +:

?q=C%2B%2B%20programming%20%26%20algorithms

Without encoding, the & would be interpreted as a query parameter separator, breaking the intended meaning.

How Percent-Encoding Works

Percent-encoding converts each byte of the character (in UTF-8) to a two-digit hexadecimal value prefixed with %.

CharacterUTF-8 bytesPercent-encoded
Space0x20%20
+0x2B%2B
/0x2F%2F
@0x40%40
#0x23%23
&0x26%26
=0x3D%3D
?0x3F%3F

For multi-byte Unicode characters (emoji, Arabic, Chinese, etc.), each byte is encoded separately:

The Arabic word مرحبا ("hello") in UTF-8 is six bytes, encoded as:

%D9%85%D8%B1%D8%AD%D8%A8%D8%A7

Reserved vs. Unreserved Characters

The URL specification divides characters into three categories:

Unreserved characters — safe in any part of a URL, never need encoding:

A-Z a-z 0-9 - _ . ~

Reserved characters — have special meaning in URL structure. Must be encoded when used as data, but not when used as delimiters:

: / ? # [ ] @ ! $ & ' ( ) * + , ; =

All other characters — must always be encoded.

encodeURI vs. encodeURIComponent

JavaScript provides two built-in encoding functions, and choosing the wrong one is a very common source of bugs.

encodeURI()

Encodes a complete URL. It does NOT encode reserved characters because they are needed as structural delimiters:

encodeURI('https://example.com/search?q=hello world&lang=en')
// Output: 'https://example.com/search?q=hello%20world&lang=en'

The ://, /, ?, and & are preserved because they structure the URL.

Use this: When you have a full URL that you need to make safe for transmission.

encodeURIComponent()

Encodes a URL component (a single value, not the full URL). It DOES encode reserved characters:

encodeURIComponent('hello world & more')
// Output: 'hello%20world%20%26%20more'

encodeURIComponent('C++')
// Output: 'C%2B%2B'

Use this: When encoding individual query parameter values, form field values, or path segments that you are dynamically inserting into a URL.

The Most Common Mistake

Building query strings manually without encodeURIComponent:

// WRONG — if name contains &, ?, =, or spaces, the URL breaks
const url = `https://api.example.com/users?name=${name}&city=${city}`;

// CORRECT
const url = `https://api.example.com/users?name=${encodeURIComponent(name)}&city=${encodeURIComponent(city)}`;

Or better yet, use URLSearchParams which handles encoding automatically:

const params = new URLSearchParams({ name, city });
const url = `https://api.example.com/users?${params}`;

Spaces: %20 vs. +

In query strings, spaces are sometimes encoded as + instead of %20. This is a convention from HTML form encoding (the application/x-www-form-urlencoded content type) where the browser encodes form submissions with + for spaces.

Most web servers and frameworks accept both + and %20 for spaces in query strings, but %20 is the technically correct representation per RFC 3986. Outside of query strings (in path segments, for example), only %20 is valid — + in a URL path means a literal plus sign.

Best practice: Use %20 (via encodeURIComponent) unless you are specifically implementing HTML form encoding.

Decoding URL-Encoded Strings

Decoding reverses the process:

decodeURIComponent('hello%20world%20%26%20more')
// Output: 'hello world & more'

decodeURI('https://example.com/search?q=hello%20world')
// Output: 'https://example.com/search?q=hello world'

Our URL Encoder/Decoder tool handles both encoding and decoding instantly, including multi-byte Unicode characters, with no data leaving your browser.

Double-Encoding: A Frequent Bug

Double-encoding happens when encoded text gets encoded again:

hello world → hello%20world → hello%2520world

%25 is the encoding for the % character itself, so %2520 decodes to %20, not a space. This causes "URL not found" errors or garbled query parameters.

Common cause: Calling encodeURIComponent twice, or encoding a URL that already contains encoded characters. Always work with decoded (raw) values before encoding, and encode exactly once before including in a URL.

Unicode and International Characters

Modern web applications support international users whose names, addresses, and search queries contain characters outside ASCII. URL encoding handles all Unicode correctly via UTF-8:

encodeURIComponent('日本語')  // Japanese: "Japanese language"
// Output: '%E6%97%A5%E6%9C%AC%E8%AA%9E'

encodeURIComponent('مرحبا')  // Arabic: "Hello"
// Output: '%D9%85%D8%B1%D8%AD%D8%A8%D8%A7'

Modern browsers display internationalized domain names (IDN) and encoded paths in their decoded form in the address bar for readability, but the actual HTTP request uses the encoded form.

Summary

URL encoding ensures that any data can be safely transmitted as part of a URL without ambiguity or corruption.

Key takeaways:

  • Only letters, digits, -, _, ., ~ are safe in a URL without encoding
  • Use encodeURIComponent() for individual values (query parameters, path segments)
  • Use encodeURI() only for full URLs
  • Prefer URLSearchParams to avoid manual encoding mistakes
  • Spaces are %20 in paths; + is acceptable only in query strings
  • Avoid double-encoding — encode raw values exactly once
  • Encode and decode instantly with the URL Encoder/Decoder tool