Every time you search Google, share a link, or call an API with query parameters, URL encoding is working silently in the background. Understanding it prevents bugs in web applications and APIs — especially when dealing with special characters, Thai text, and query strings.
What is URL Encoding?
URLs can only contain a safe subset of ASCII characters. Characters outside this safe set — spaces, Thai letters, symbols like &, =, and # — must be encoded before being placed in a URL. This encoding is called percent-encoding because each unsafe character is replaced by a % sign followed by its two-digit hexadecimal ASCII code.
For example:
- Space →
%20(ASCII 32 = 0x20) &→%26=→%3D#→%23- Thai ก →
%E0%B8%81(UTF-8 encoded, then percent-encoded)
Characters That Don't Need Encoding
The following characters are "unreserved" and are safe to use in URLs without encoding:
A–Z a–z 0–9 - _ . ~Everything else — including spaces, Thai characters, emoji, and most punctuation — must be encoded when used in a URL value.
encodeURI vs encodeURIComponent
JavaScript provides two built-in functions for URL encoding. Choosing the wrong one is one of the most common sources of URL-related bugs.
encodeURI()
Encodes a complete URL. It leaves characters that are legal in a URL structure untouched — things like /, ?, &, #, and : — because removing those would break the URL's meaning.
encodeURI('https://example.com/search?q=hello world')
// → 'https://example.com/search?q=hello%20world'
// These are NOT encoded by encodeURI:
// : / ? # [ ] @ ! $ & ' ( ) * + , ; =encodeURIComponent()
Encodes a single value to be placed inside a URL — a query parameter value, a path segment, or a fragment. It encodes everything except A–Z a–z 0–9 - _ . ~, including all the URL-structural characters that encodeURI skips.
encodeURIComponent('hello world & more')
// → 'hello%20world%20%26%20more'
encodeURIComponent('https://example.com')
// → 'https%3A%2F%2Fexample.com'
// (note: the :// are also encoded — correct for a value)The Rule of Thumb
- Use
encodeURIComponent()on parameter values before appending them to a URL. - Use
encodeURI()only if you have a complete URL string with no pre-encoded components. - In practice, URLSearchParams handles encoding automatically and is the safest approach.
// Best practice — use URLSearchParams
const params = new URLSearchParams({
q: 'hello world',
lang: 'th',
ref: 'https://example.com',
})
const url = `https://api.example.com/search?${params}`
// → https://api.example.com/search?q=hello+world&lang=th&ref=https%3A%2F%2Fexample.comNote: URLSearchParams encodes spaces as + rather than %20. Both are valid in query strings; most servers accept either.
Decoding URL-Encoded Strings
The corresponding decode functions are:
decodeURI('hello%20world') // → 'hello world'
decodeURIComponent('hello%20world') // → 'hello world'
// Decode a full URL
decodeURI('https://example.com/path?q=hello%20world')
// → 'https://example.com/path?q=hello world'Thai Characters and URL Encoding
Thai characters are multi-byte in UTF-8. The string "สวัสดี" becomes a sequence of percent-encoded UTF-8 bytes:
encodeURIComponent('สวัสดี')
// → '%E0%B8%AA%E0%B8%A7%E0%B8%B1%E0%B8%AA%E0%B8%94%E0%B8%B5'Modern browsers handle this transparently — the address bar displays "สวัสดี" even though the underlying request uses the percent-encoded form. APIs, however, receive the encoded form and must decode it on the server side.
Common URL Encoding Mistakes
| Mistake | Problem | Fix |
|---|---|---|
Not encoding & in values | Server splits parameter at the & | Use encodeURIComponent() on each value |
| Double-encoding | %20 becomes %2520 | Decode first if input might already be encoded |
Using encodeURI on a value | & and = aren't encoded, breaking params | Use encodeURIComponent() for values |
| Not encoding spaces in paths | Some servers reject paths with literal spaces | Encode spaces as %20 in paths |
URL Encoding in Other Languages
# Python
from urllib.parse import quote, urlencode
quote('hello world') # → 'hello%20world'
urlencode({'q': 'hello world', 'n': 5}) # → 'q=hello+world&n=5'
# PHP
urlencode('hello world') // → 'hello+world'
rawurlencode('hello world') // → 'hello%20world'
# Go
url.QueryEscape("hello world") // → "hello+world"
url.PathEscape("hello world") // → "hello%20world"