URL Encoder & Decoder
How it works
encodeURI vs encodeURIComponent
encodeURIComponent is for a value going into a URL: it escapes the separators : / ? & = too, so 'a=1&b=2' becomes a%3D1%26b%3D2 and survives as a single query parameter. encodeURI is for a URL that is already assembled: it keeps :// and ? intact and only escapes characters that cannot appear in a URL at all, like spaces and non-ASCII. Running a full URL through encodeURIComponent is the classic bug - https://x.dev/a b?q=1 turns into https%3A%2F%2Fx.dev%2Fa%20b%3Fq%3D1, which no longer works as a link. The 'Encode component' and 'Encode URL' modes map exactly to those two functions.
The five characters encodeURIComponent refuses to escape
encodeURIComponent leaves ! ' ( ) * untouched: ECMAScript's unescaped set includes those marks, and RFC 3986 classes them as sub-delims that are legal in a URL. Most servers do not care - until you hit a context that demands the stricter RFC 3986 unreserved set (only A-Z a-z 0-9 - . _ ~): OAuth 1.0 signature base strings (RFC 5849), AWS SigV4 canonical requests, or filenames with apostrophes. The strict option applies the missing escapes: %21 %27 %28 %29 %2A.
+ means space only in form data
In application/x-www-form-urlencoded - the encoding HTML forms use for query strings and POST bodies - a space becomes '+'. In a URL path, '+' is a literal plus sign per RFC 3986. That is why decodeURIComponent('a+b') returns 'a+b', not 'a b': the JS built-ins never touch '+'. The form option here uses the exact WHATWG form-urlencoded serializer (space to +, ~ to %7E, * kept literal) for encoding, and converts + back to a space before decoding.
URIError: URI malformed
decodeURIComponent throws a URIError in two situations: a % that is not followed by two hex digits ('100%' or a truncated '%e'), and %XX bytes that do not form valid UTF-8 ('%FF' on its own). The browser message does not say which, or where. This decoder scans first and reports the exact index of a bad escape, and names invalid UTF-8 separately. If your text legitimately contains a raw % sign, it was never percent-encoded to begin with - encode it (% becomes %25) instead of decoding.
Examples
| it's (new)!* | it's%20(new)!* |
| 100% legit | 100%25%20legit |
| a=1&b=2 | a%3D1%26b%3D2 |
| 安静 👋 | %E5%AE%89%E9%9D%99%20%F0%9F%91%8B |
| café | caf%C3%A9 |
FAQ
Why does decodeURIComponent throw 'URIError: URI malformed'?
Two causes: a % not followed by exactly two hex digits (a lone '100%', a truncated '%e'), or a structurally valid %XX sequence whose bytes are not valid UTF-8 (like '%FF' by itself). The built-in error does not tell you which one or where. This decoder reports the index of the bad escape and distinguishes the two cases.
Should I use encodeURI or encodeURIComponent?
Encoding a value to insert into a URL (a query parameter, a path segment): encodeURIComponent, so & = / ? are escaped and cannot restructure the URL. Cleaning up an already-assembled URL: encodeURI, which keeps :// and ? intact. Never pass a full URL through encodeURIComponent - it escapes the separators too.
Why is + not decoded as a space?
'+' means space only in application/x-www-form-urlencoded, the encoding HTML forms use for query strings and POST bodies. In a URL path it is a literal plus (RFC 3986). decodeURIComponent never converts '+' - if your data came from a query string, enable the form encoding option here.
Why does encodeURIComponent not escape apostrophes and parentheses?
ECMAScript defines an unescaped set that includes the marks ! ~ * ' ( ), and RFC 3986 lists ! ' ( ) * among the legal sub-delims. Contexts that require the stricter unreserved-only rule - OAuth 1.0 signatures (RFC 5849) and AWS SigV4 canonicalization - reject that. The strict RFC 3986 option escapes them to %21 %27 %28 %29 %2A.
Is my URL uploaded to a server?
No. The page ships a strict Content-Security-Policy: connect-src 'none' blocks fetch, XHR, WebSockets and beacons, while default-src 'self' and form-action 'none' block every other way markup could send data off-site. Encoding happens in your browser and cannot leave it.