URL Encode / Decode
Free online URL encoder and decoder. Convert text to URL-encoded format and decode URL-encoded strings back to plain text. UTF-8 safe with no garbled characters. All processing happens locally in your browser.
Description:
URL encoding is byte transcription, not character hiding
It is tempting to think of percent-encoding as "replacing the troublesome characters" - spaces, Chinese text, ampersands. That intuition is only half right. The precise meaning of percent-encoding is: first split the character into bytes according to an agreed character set (almost always UTF-8 today), then write each byte as a percent sign followed by two uppercase hexadecimal digits. So %E4%BD%A0 is not a magic code for the Chinese character 你; it is simply another notation for the three UTF-8 bytes E4 BD A0 that represent 你.
Once you internalize that "encoding is about bytes, not characters", most confusion disappears: why the same Chinese text sometimes mojibakes on another system (a character-set mismatch), why re-encoding an already percent-encoded string keeps growing (double encoding), and why an emoji expands so much (four bytes). This tool strictly uses the browser's built-in encodeURIComponent and decodeURIComponent over UTF-8, so there is no character-set mismatch and no mojibake. For more on handling such parameters on the front end, see the complete URL encoding guide.
RFC 3986 character classes: what never needs encoding, what must be encoded
RFC 3986 (Uniform Resource Identifier: Generic Syntax) divides the characters allowed in a URI into three classes. The key point: unreserved characters may appear verbatim anywhere and never need encoding; reserved characters are reserved for their delimiter role, and must be encoded only when they appear in a position that conflicts with that role; everything else (space, %, quotes, angle brackets, backslash, braces, and so on) must always be encoded. The table below shows how this tool (encodeURIComponent) actually treats each class - note it is stricter than RFC 3986: it encodes nearly all reserved characters, leaving only the unreserved set plus a few sub-delimiters that happen to be exempt (! ' ( ) * - . _ ~).
| Class | Characters | Count | Must encode? | encodeURIComponent behavior |
|---|---|---|---|---|
| Unreserved | A-Z a-z 0-9 - . _ ~ | 66 | No (verbatim is fine) | All left unencoded |
| Reserved: gen-delims | : / ? # [ ] @ | 7 | Must encode outside its delimiter role | All encoded (: becomes %3A, etc.) |
| Reserved: sub-delims | ! $ & ' ( ) * + , ; = | 11 | Encode when used as data | Encoded except ! ' ( ) * |
| Everything else (control/punct) | space % < > \ ^ ` { | } etc. | rest | Must encode | All encoded (space to %20, % to %25) |
Note: Encoding an unreserved character anyway (writing a as %61) is still semantically equivalent - the server decodes it back. But mis-encoding a reserved character can break the URL structure. So whether to encode a reserved character inside a value depends on whether that character is acting as a delimiter or as data.
path vs query vs fragment: the same character encodes differently
The same character is allowed to appear verbatim in different parts of a URL for different reasons. RFC 3986's ABNF defines it: the path allows pchar (unreserved plus sub-delims plus : @ /); the query and fragment allow a wider set (the fragment even allows ?). But "allowed by syntax" is not "safe in practice" - inside a query, & and = may be written verbatim yet act as parameter delimiters; to pass them as data you must encode them. The table below pairs each fact with this tool's output for reference:
| Char | path | query | fragment | encodeURIComponent | Note |
|---|---|---|---|---|---|
| + | verbatim | verbatim | verbatim | %2B | In form encoding + means space; encode it as data |
| / | verbatim (segment sep) | verbatim | verbatim | %2F | Hierarchy separator in path |
| ? | must encode | verbatim | verbatim | %3F | ? in path is misread as query start |
| & | verbatim | verbatim | verbatim | %26 | Query param separator; encode as data |
| = | verbatim | verbatim | verbatim | %3D | Key/value separator; encode as data |
| # | must encode | must encode | verbatim (fragment start) | %23 | # must be encoded in any data position |
| : | verbatim | verbatim | verbatim | %3A | Used for scheme and port |
| @ | verbatim | verbatim | verbatim | %40 | Used for userinfo |
| [ ] | must encode | must encode | must encode | %5B %5D | Reserved for IPv6 literals; encode as data |
Note: "Verbatim" above is a syntax statement. When encoding a URL as a "whole address", use encodeURI (it keeps the structural characters); when encoding "one parameter value", use this tool's encodeURIComponent (it encodes the structural characters too). Mixing the two either breaks the address structure or lets special characters slip through. When handling binary or complex text with the Base64 encoder, Base64-then-parameter is often the cleaner route.
UTF-8 multi-byte reference: one Chinese character = 9 percent characters
The length of percent-encoding depends on how many bytes the character takes in UTF-8: each byte becomes a percent sign plus two hex digits, so bytes times 3 equals the length of the percent form. Common CJK ideographs sit in the Basic Multilingual Plane (BMP) and take 3 bytes each; German umlauts and some European characters sit in the Latin-1 Supplement and take 2 bytes; emoji sit in the Supplementary Plane and take 4 bytes. All numbers below are measured with Node's Buffer under UTF-8:
| Sample | Chars | UTF-8 bytes | % form length | Encoded (excerpt) |
|---|---|---|---|---|
| 你 | 1 | 3 | 9 | %E4%BD%A0 |
| 你好世界 | 4 | 12 | 36 | %E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C |
| ö (German) | 1 | 2 | 6 | %C3%B6 |
| ß (German) | 1 | 2 | 6 | %C3%9F |
| あ (Japanese kana) | 1 | 3 | 9 | %E3%81%82 |
| こんにちは (Japanese) | 5 | 15 | 45 | %E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF |
| U+1F600 (emoji, grinning face) | 1 | 4 | 12 | %F0%9F%98%80 |
| U+1F680 (emoji, rocket) | 1 | 4 | 12 | %F0%9F%9A%80 |
Note: The byte count is decided by the character set. If a legacy system uses GBK instead of UTF-8, 你 becomes 2 bytes (%C4%E3) rather than 3. This tool is fixed to UTF-8, which matches RFC 3986 and the WHATWG URL Standard and is the key to avoiding mojibake. To measure such text length first, the word counter shows the character-versus-byte difference clearly.
encodeURI vs encodeURIComponent vs escape
This is where developers trip up most. The three functions leave entirely different character sets unencoded, and escape has long been deprecated (discouraged since ES3, explicitly discouraged by WHATWG). The crucial difference: escape does not encode in UTF-8 - for non-ASCII characters it emits a %uXXXX UTF-16 code unit that servers almost never decode correctly, inviting mojibake. Take the test string https://example.com/p?q=1&x=a b+c#frag(ok)~*.中:
| Function | Characters left unencoded | Treatment of 中 | UTF-8 safe? |
|---|---|---|---|
| encodeURI | Unreserved plus all reserved (except a few like %) | %E4%B8%AD (correct) | Yes |
| encodeURIComponent | Only unreserved plus ! ' ( ) * - . _ ~ | %E4%B8%AD (correct) | Yes (this tool) |
| escape | @ * + - _ . / and alphanumerics | %u4E2D (not UTF-8) | No (deprecated) |
The three outputs for the same test string (key differences):
encodeURIyieldshttps://example.com/p?q=1&x=a%20b+c#frag(ok)~*.%E4%B8%AD(keeps : / ? # & = + ( ) ~ *, encodes only the space and the Chinese)encodeURIComponentyieldshttps%3A%2F%2Fexample.com%2Fp%3Fq%3D1%26x%3Da%20b%2Bc%23frag(ok)~*.%E4%B8%AD(encodes almost everything, space stays %20)escapeyieldshttps%3A//example.com/p%3Fq%3D1%26x%3Da%20b+c%23frag%28ok%29%7E*.%u4E2D(Chinese becomes %u4E2D, slash and + left unencoded)
Note: In new code, always use encodeURIComponent for "parameter values" and encodeURI for "whole URLs". escape appears only in very old scripts and must not be used in new projects. To strip HTML tags before encoding, clean the text first with the HTML stripper.
Form encoding: why a space is + in a form but %20 in a path
application/x-www-form-urlencoded is the default submission format for HTML forms. Its historical convention comes from early email and CGI: a space is written as + (plus), while other special characters still use percent-encoding. A space inside a URL path (path), by the percent-encoding rule, is written directly as %20. Both are "legal" but in different contexts:
| Context | Space written as | Why |
|---|---|---|
| URL path / query raw percent-encoding | %20 | RFC 3986 requires spaces to be encoded; %20 is the standard form |
| application/x-www-form-urlencoded body | + | HTML and the WHATWG form spec treat + as a space shorthand |
Measured: calling encodeURIComponent on a b c gives a%20b%20c; if you then apply the form rule and replace %20 with +, you get a+b+c. This is the source of the common "I encoded the space but received a plus" mystery - the server turns a + in the form body back into a space, yet treats a + in the path as a literal plus. Whether you sent a space or a plus depends on whether it lived in the form body or the URL.
Three worked examples (consistent with the page default)
All three examples use the same encodeURIComponent plus UTF-8 logic this tool uses; every output is measured by a script using the page's exact formula and can be reproduced in the input box above.
Example 1: the default input is already encoded - the double-encoding trap
The page's default input is https://example.com/search?q=hello%20world. Note that %20 is already an encoded space. If you "encode it once more on a whim" (say, stuffing it as plain text into another parameter), the % itself gets encoded to %25, and the result becomes:
Encoded: https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dhello%2520world Double-enc: https%253A%252F%252Fexample.com%252Fsearch%253Fq%253Dhello%252520world
Decoding this tool's encoded result restores the original string perfectly (decodeURIComponent is reversible); but if you pass the "double-encoded" result straight in as a parameter, the server decodes once and still gets the literal hello%20world instead of a space - the classic double-encoding bug. Note: Encode only when the input is genuinely "raw text"; do not re-encode a string that is already encoded.
Example 2: byte expansion of the Chinese 你好世界
Input 你好世界 (four Han characters), UTF-8 at 3 bytes each, encodes to:
%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C
That is 12 bytes and 36 percent-form characters. Decoding returns 你好世界, identical to the input. This is exactly why people say "a Chinese character takes 9 characters in a URL" (3 bytes times 3 display characters per byte).
Example 3: a value with structural characters, price=9.9&q=a/b c#tag
Input price=9.9&q=a/b c#tag, where & = / # and the space are all structural. Encoded as a "single parameter value", all are escaped:
price%3D9.9%26q%3Da%2Fb%20c%23tag
Decoding restores price=9.9&q=a/b c#tag. Without encoding, the server would treat &q= as a new parameter and #tag as a fragment, truncating the original data.
Frequently asked questions (specific to this tool)
- Why must I not re-encode an already percent-encoded string like hello%20world?
- Because it is already a percent-encoded string and % is an ordinary character in it. Re-encoding turns % into %25 and the original %20 into %2520; the server, decoding only once, gets the literal hello%20world instead of a space. Only raw text needs encoding; an already-encoded string should be decoded or used as-is.
- Why do the same characters get encoded differently in path, query, and fragment?
- Because RFC 3986 defines a different legal character set for each position: the path allows pchar (sub-delims plus : @ /), while query and fragment allow a wider set (the fragment even allows ?). But syntax allowance is not semantic safety - & = act as delimiters in a query and must be encoded to be passed as data. This tool uses encodeURIComponent, which encodes the structural characters too, fitting a "parameter value".
- How many percent-encoded characters does one Chinese character become?
- Under UTF-8, a common Han character takes 3 bytes; each byte is written as %XX, so one Han character equals 3 %XX equals 9 display characters (e.g. 你 becomes %E4%BD%A0). German umlauts in the Latin-1 range take 2 bytes (6 characters); emoji take 4 bytes (12 characters). The length is set by the character set, not the character itself.
- When should I use encodeURI versus encodeURIComponent versus escape?
- Use encodeURI for a "whole URL address" (it keeps : / ? # & = and other structural characters, preserving the address); use encodeURIComponent for "one parameter value" (it encodes the structural characters too, preventing special characters from being misread). This tool corresponds to the latter. escape is deprecated and must not be used in new code.
- Why does a space become + in form data but %20 in a path?
- The application/x-www-form-urlencoded form body follows the old convention of writing a space as +; the raw percent-encoding of a URL path and query writes a space as %20. The contexts differ: the server turns a + in the form body back into a space, yet treats a + in the path as a literal plus. So whether you sent a space or a plus depends on whether it lived in the form body or the URL.