WWeHelpDevs
TutorialAugust 17, 2026·6 min read

URL Encoding Explained: How to Encode and Decode URLs

Learn what URL encoding (percent encoding) is, why it's needed, how to encode and decode URLs in JavaScript, Python, and the terminal, and common mistakes to avoid.

URL encoding is one of those things that seems simple until you encounter a bug where a space in a query parameter breaks your entire API call. This guide explains the rules, the edge cases, and how to handle encoding correctly in every language.

What is URL Encoding?

URLs can only contain a limited set of characters defined in RFC 3986. Characters outside this set — spaces, Unicode letters, symbols, reserved characters used as delimiters — must be encoded as %XX where XX is the hexadecimal value of the byte.

This encoding is called percent encoding (commonly called "URL encoding").

For example:

  • Space ( ) → %20
  • #%23
  • &%26
  • =%3D
  • @%40
  • é%C3%A9 (UTF-8 bytes: 0xC3, 0xA9)

Safe vs. Reserved vs. Unreserved Characters

Unreserved characters — never need encoding:

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

Reserved characters — have special meaning in URLs; encode them when used as data:

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

Everything else — must be encoded, including spaces and non-ASCII characters.

Spaces: %20 vs +

This is the most common source of confusion:

  • %20 is the RFC 3986 percent-encoded form of a space — valid everywhere in a URL
  • + means a space only in HTML form data (application/x-www-form-urlencoded) — i.e., query strings submitted by HTML forms

In practice:

  • If you're building a query string from an HTML form: + is fine
  • If you're building a URL in code: use %20 (or use the right encoding function)

Encoding in JavaScript

JavaScript has three URL encoding functions, and they do different things:

// encodeURIComponent — encodes everything except: A-Za-z0-9 - _ . ! ~ * ' ( )
// USE THIS for encoding individual query parameter values
encodeURIComponent('hello world & more')
// → 'hello%20world%20%26%20more'

encodeURIComponent('user@example.com')
// → 'user%40example.com'

// encodeURI — leaves reserved characters and # unencoded
// USE THIS for encoding a full URL (preserves structure)
encodeURI('https://example.com/search?q=hello world')
// → 'https://example.com/search?q=hello%20world'

// escape() — DEPRECATED, do not use
// It doesn't handle non-ASCII correctly

The rule: use encodeURIComponent for values, encodeURI for full URLs.

// Building a query string correctly
const params = new URLSearchParams({
  q: 'hello world',
  filter: 'type=article&status=published',
  tag: 'c++',
})
const url = `https://example.com/search?${params.toString()}`
// → https://example.com/search?q=hello+world&filter=type%3Darticle%26status%3Dpublished&tag=c%2B%2B

URLSearchParams is the modern, correct way to build query strings — it handles all encoding automatically.

Decoding:

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

decodeURIComponent('%C3%A9')
// → 'é'

Encoding in Python

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

# quote — encodes for URL paths (leaves / unencoded by default)
quote('hello world')           # 'hello%20world'
quote('hello/world/path')      # 'hello/world/path'  ← / is kept
quote('hello/world', safe='') # 'hello%2Fworld'      ← / is encoded

# quote_plus — encodes for query strings (space → +)
quote_plus('hello world')     # 'hello+world'
quote_plus('a=1&b=2')         # 'a%3D1%26b%3D2'

# Building a query string
params = {'q': 'hello world', 'page': 1, 'filter': 'a&b'}
urlencode(params)
# → 'q=hello+world&page=1&filter=a%26b'

# Decoding
unquote('hello%20world')      # 'hello world'
unquote('%C3%A9')             # 'é'

Encoding in the Terminal

# Using Python (available on virtually all systems)
python3 -c "import urllib.parse; print(urllib.parse.quote('hello world & more'))"
# hello%20world%20%26%20more

# Using curl --data-urlencode (for POST data)
curl -G "https://api.example.com/search" --data-urlencode "q=hello world & more"

# Using node
node -e "console.log(encodeURIComponent('hello world & more'))"
# hello%20world%20%26%20more

# Using jq
echo '"hello world & more"' | jq -r '@uri'
# hello%20world%20%2526%20more

Common Mistakes

Double encoding — encoding an already-encoded URL:

// WRONG: encodes %20 again → %2520
encodeURIComponent(encodeURIComponent('hello world'))
// → 'hello%2520world'

// Correct: decode first if input may already be encoded
const safe = decodeURIComponent(input) // decode
const encoded = encodeURIComponent(safe) // re-encode once

Encoding the whole URL including structure:

// WRONG: breaks the URL
encodeURIComponent('https://example.com/search?q=hello')
// → 'https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dhello'

// Correct: encode only the value
`https://example.com/search?q=${encodeURIComponent('hello')}`

Forgetting to encode user input in query parameters:

// WRONG: breaks if user types & or =
const url = `https://api.example.com?q=${userInput}`

// Correct
const url = `https://api.example.com?q=${encodeURIComponent(userInput)}`

URL Encoding vs Base64

Both encode data as ASCII text, but for different purposes:

  • URL encoding → makes arbitrary text safe for use inside a URL
  • Base64 → makes binary data safe for use inside text (JSON, HTML, email)

Don't use Base64 for URL parameters — the output contains +, /, and = which still need percent-encoding, making it worse than just URL-encoding the original value.

Try It Online

Use our free URL Encoder/Decoder to encode and decode URL strings instantly in your browser.

Summary

  • Percent encoding converts unsafe characters to %XX hex sequences
  • Use encodeURIComponent for individual values, encodeURI for full URLs, URLSearchParams for query strings
  • + means space only in form data — prefer %20 in code
  • Never encode an already-encoded URL (causes double encoding → %2520)
  • Always encode user-supplied input before inserting into URLs

References & Credits

← All articles

Enjoyed this article?

Get new tutorials and guides in your inbox every week. Free, no spam.

Subscribe to WeHelpDevs →