You build a search endpoint that takes a query parameter. Everything works until a user searches for C++ tutorials and the request breaks. The plus signs disappear. The URL you construct looks fine in your code, but what actually gets sent to the server is C tutorials with two extra + characters that are now interpreted as spaces somewhere in the chain.
The URL Encode / Decode tool at ToolCenterHub encodes and decodes strings in your browser. Paste a raw string to get the percent-encoded version, or paste a percent-encoded value to decode it back to plain text. This guide explains how percent encoding works, when to use each JavaScript encoding function, which characters require encoding and why, and the most common encoding bugs that break API integrations.
What Is URL Encoding and Why Does It Exist?
A URL can only contain a specific set of characters: letters, digits, and a small set of symbols that have defined roles in URL syntax. Hyphens, underscores, periods, and tildes are safe. Forward slashes separate path segments. Question marks start query strings. Ampersands separate query parameters. Hash symbols start fragment identifiers.
Every other character must be encoded before it can appear in a URL. Encoding converts the character to a percent sign followed by two uppercase hexadecimal digits representing the character's byte value in UTF-8. A space has a byte value of 32, which is 20 in hexadecimal, so it becomes %20. The copyright symbol is a two-byte UTF-8 sequence (0xC2 0xA9) so it becomes %C2%A9.
This matters because URLs are transmitted as plain text over HTTP. If a URL contains a space or a non-ASCII character, different browsers, proxies, and servers may interpret or encode it differently. Percent encoding defines one unambiguous representation for every character, which means a percent-encoded URL looks the same everywhere in the transmission chain.
The formal standard is RFC 3986, which defines the URL syntax and specifies exactly which characters must be encoded, which are allowed unencoded, and which are reserved as delimiters. Understanding this standard explains why there are two different encoding functions in JavaScript instead of one.
How Does URL Encoding Work?
Encoding a string to a percent-encoded URL component happens in two steps. First, the string is converted to bytes using UTF-8 encoding. Second, each byte that is not in the unreserved character set is replaced with % followed by two hexadecimal digits.
The unreserved characters in RFC 3986 are: A-Z, a-z, 0-9, hyphen, underscore, period, and tilde. These never need encoding. Everything else either must be encoded or is a reserved character that means something specific in URL structure. This is why UUIDs appear safely in URL path segments without any encoding: they contain only hex digits and hyphens, both of which are unreserved.
Reserved characters fall into two groups: general delimiters (:, /, ?, #, [, ], @) and sub-delimiters (!, $, &, ', (, ), *, +, ,, ;, =). When these characters appear as URL structure (a slash separating path segments, an ampersand separating parameters), they are left unencoded. When they appear as literal values within a component (an ampersand inside a parameter value), they must be encoded.
This is the distinction that drives the two JavaScript encoding functions.
What Is the Difference Between encodeURI and encodeURIComponent?
JavaScript has two encoding functions, and choosing the wrong one breaks URLs in ways that are not always obvious.
encodeURI(url) encodes a complete URL. It leaves all characters that have structural meaning in a URL unencoded: colons, slashes, question marks, hash symbols, brackets, ampersands, equals signs, and the other sub-delimiters. Use encodeURI when you have a complete URL string that might contain non-ASCII characters or spaces, but where the URL structure is already correct.
encodeURI("https://example.com/search?q=hello world&lang=en")
// Result: "https://example.com/search?q=hello%20world&lang=en"
// The & and = are left unencoded because they're URL structure
encodeURIComponent(value) encodes a single URL component. It encodes everything except the unreserved characters (letters, digits, hyphen, underscore, period, tilde). It encodes reserved characters including &, =, +, #, and /. Use encodeURIComponent when encoding a value that will be inserted into an existing URL as a query parameter value or path segment.
encodeURIComponent("hello world & more")
// Result: "hello%20world%20%26%20more"
// The & is encoded because it would break the query string if left raw
const q = encodeURIComponent(userInput);
const url = `https://example.com/search?q=${q}`;
The most common mistake is using encodeURI on a parameter value. It leaves & and = unencoded, which breaks the parameter structure. The correct pattern is always encodeURIComponent for values, encodeURI for complete URLs, and usually neither for anything you did not build yourself.
For most URL construction in modern JavaScript, URLSearchParams is the better choice over calling encodeURIComponent directly. Pass each value with params.set('key', value) and encoding happens automatically. Manual encodeURIComponent calls are correct but easy to apply inconsistently. One unencoded parameter in a URL string can break the entire query silently, with no error thrown at the point of the bug.

Which Characters Need Encoding? A Reference Table
This table shows the characters that most commonly cause bugs in URL handling, with their encoded form and the context where they appear:
| Character | Encoded Form | Why It Needs Encoding |
|---|---|---|
| Space | %20 | Not allowed in URLs; breaks parsing everywhere |
+ | %2B | Interpreted as a space in form-encoded query strings |
& | %26 | Separates query parameters; breaks if in a value |
= | %3D | Separates parameter names from values |
# | %23 | Starts the fragment; everything after it is ignored by the server |
% | %25 | The encoding prefix itself must be encoded to appear literally |
/ | %2F | Path separator; changes path depth if unencoded in a segment |
? | %3F | Starts the query string; breaks path parsing |
@ | %40 | User info delimiter in authority component |
: | %3A | Scheme delimiter and port separator |
, | %2C | Sub-delimiter with context-dependent meaning |
[ ] | %5B %5D | IPv6 address delimiters; illegal in most path contexts |
" | %22 | Not a URL character; breaks HTML attribute values |
< > | %3C %3E | Not URL characters; cause HTML parsing problems |
| Non-ASCII | Multi-byte %XX%XX | Must be UTF-8 encoded then percent-encoded |
The characters that cause the most production bugs are #, +, and %. A # in a parameter value silently truncates the server-visible portion of the URL. An unencoded % followed by two hex digits gets interpreted as an encoding sequence. A + in a value gets decoded to a space by some servers and stays as a literal + by others, creating inconsistent behavior.
Space as %20 vs Space as +: Why Both Exist
This is the source of one of the most persistent URL encoding bugs, and it comes from two different standards that overlap.
RFC 3986 defines the URL standard. In this specification, a literal space in a URL must be encoded as %20. encodeURIComponent follows this standard.
The application/x-www-form-urlencoded format is an older format used for HTML form submission. In this format, spaces are encoded as + rather than %20. When you submit an HTML form with method="GET", the browser encodes the form values using this format, so spaces become + in the query string.
The two formats are not interchangeable. A server that expects %20 may or may not handle + correctly, and vice versa. When parsing + in a query string, PHP's $_GET superglobal decodes it as a space. JavaScript's URLSearchParams also decodes + as a space. But decodeURIComponent does not: it leaves + as a literal plus sign.
The correct conversion if you receive a form-encoded string and need to decode it in JavaScript:
// Replace + with %20 before calling decodeURIComponent
const decoded = decodeURIComponent(encodedValue.replace(/\+/g, '%20'));
Or use URLSearchParams which handles this automatically:
const params = new URLSearchParams(queryString);
const value = params.get('q'); // automatically decodes both %20 and +
How to Decode a URL-Encoded String
Decoding reverses the process: %XX sequences are replaced with their corresponding characters. In JavaScript, use decodeURIComponent() for component values and decodeURI() for full URLs.
Do not use unescape() even though it works for basic ASCII. It is deprecated, it does not handle multi-byte UTF-8 sequences correctly, and it treats + as a literal plus rather than a space.
For double-encoded URLs (where a percent sign was itself encoded, producing %2520 instead of %20), you need to decode twice:
decodeURIComponent(decodeURIComponent('%2520'))
// First decode: '%2520' → '%20'
// Second decode: '%20' → ' '
Double encoding happens when code encodes a value that is already encoded. Always check whether the input is already encoded before encoding again. A string that contains %20 is already encoded and should be decoded first, not encoded again.
Double encoding is a frequent source of bugs in systems that pass URL parameters through multiple layers: a frontend encodes a value, passes it to a backend route handler, which then encodes it again before forwarding it to a third-party API. The third-party API receives %2520 and either fails or returns a result for the literal string %20 rather than the intended space. If you are debugging an integration where parameter values arrive corrupted, check every layer in the chain and confirm which layer is responsible for encoding. Encoding should happen exactly once, as close to the point of URL construction as possible.
The # character deserves special attention in this context. Because # signals the start of the fragment identifier, anything after an unencoded # in a URL is silently dropped by the server before the request even reaches your code. The browser handles the fragment client-side and never sends it over the network. If a query parameter value contains # and it is not encoded as %23, the server receives a truncated URL with no error and no indication that part of the value was lost.
For working with other developer tools that handle encoded data, Base64 encoding is a separate encoding scheme used for binary data. Unlike percent encoding which is character-by-character, Base64 converts binary bytes to printable ASCII using a 64-character alphabet. The two systems appear together often in URLs that embed Base64-encoded data as query parameter values, which means the Base64 string itself must then be percent-encoded when placed in the URL. The URL Encode / Decode tool handles this outer layer. When you also work with API security, hash values commonly appear as URL-encoded signatures in webhook callbacks and signed URL schemes.

