Skip to main content
Utily
Developer Tools

URL Encoder / Decoder

Encode text into a URL-safe string or decode one back, live on every keystroke. RFC 3986 and form modes, a character map of exactly what changed, and nothing leaves your browser.

Encoded
hello%20world

11 characters in → 13 characters out

Character map

11 characters

One row per character in the text input, with its Unicode code point, its class and its RFC 3986 encoded form.

CharacterCode pointClassEncoded
hU+0068unreservedh
eU+0065unreservede
lU+006Cunreservedl
lU+006Cunreservedl
oU+006Funreservedo
␣U+0020space%20
wU+0077unreservedw
oU+006Funreservedo
rU+0072unreservedr
lU+006Cunreservedl
dU+0064unreservedd

How it works

  1. 1

    Pick the direction

    The Encode tab turns text into a URL-safe string; the Decode tab turns a percent-encoded string back into text. Both run on every keystroke, so there is no convert button — switch tabs and the result is already there.

  2. 2

    Choose the mode

    RFC 3986 writes a space as %20; form mode, matching application/x-www-form-urlencoded, writes it as +. That single substitution is the entire difference between the two modes, and the selector applies to both directions.

  3. 3

    Read the live result

    The encoded or decoded value appears under the input with a copy button. A decode that cannot succeed — a stray %, or escapes that are not valid UTF-8 — says so plainly instead of producing garbage.

  4. 4

    Inspect the character map

    The panel under the result lists one row per character in the input: the character itself, its Unicode code point, its class (unreserved, reserved, space, percent or unsafe) and its encoded form. Long inputs stop at 500 rows with a note telling you how many more there are.

Two modes, one alphabet

Percent-encoding exists because a URL is a restricted alphabet wearing a longer one's clothes. RFC 3986 allows a URI to carry unreserved characters bare — letters, digits, and the four punctuation marks - . _ ~ — and defines everything else as either reserved, meaning it may carry structural meaning, or simply unsafe outside a URL context. The escape mechanism is uniform: take the character's UTF-8 bytes and write each one as a percent sign followed by two hexadecimal digits. A decoder runs the same rule backwards, which is what makes the scheme lossless for any text at all.

In practice two dialects of the scheme are in service, and they differ in exactly one place: how a space is written. RFC 3986 percent-encoding — what this page's Encode tab produces by default — writes it as %20. The older form dialect, application/x-www-form-urlencoded, was standardized around HTML form submission and writes the same space as +. Every other character encodes identically in both, which is why the mode selector changes so little and yet matters so much. The same input in each mode shows the shape of it:

InputRFC 3986 modeForm mode
hello worldhello%20worldhello+world
a+ba%2Bba%2Bb
!'()*%21%27%28%29%2A%21%27%28%29%2A
😀%F0%9F%98%80%F0%9F%98%80

Three of those rows are identical across the modes, and they repay a second look. The plus in a+b has to become %2B in both modes — in form mode because a bare plus would be read back as a space, destroying the value, and in RFC 3986 mode because the plus is reserved and this tool never leaves reserved characters bare. The punctuation row !'()* exists because JavaScript's encodeURIComponent quietly leaves those five unescaped, while RFC 3986's unreserved set does not include them; this page escapes them so its output survives the strictest parser. And the emoji row shows the byte arithmetic at work: four UTF-8 bytes, one escape each.

Decoding mirrors the same split. In form mode the decoder reads every plus as a space before resolving escapes, so a+b comes back as 'a b'; in RFC 3986 mode the plus is an ordinary reserved character and comes back a plus. That asymmetry is the single most common cause of a wrongly decoded URL parameter — a value whose pluses were literal, decoded with form mode, arrives with spaces where pluses used to be. When in doubt, decode a short sample both ways; the wrong mode announces itself immediately.

What gets escaped: the three tiers

Every character an encoder meets falls into one of three tiers, and the tier decides the output. Unreserved characters — A-Z, a-z, 0-9, - . _ ~ — pass through untouched; encoding them would only make URLs longer for no benefit. Reserved characters — : / ? # [ ] @ ! $ & ' ( ) * + , ; = — are legal in a URI but meaningful, so an encoder escapes them whenever they appear as data rather than structure. Everything else is unsafe in a URL by definition: spaces, quotation marks, angle brackets, accented letters, CJK characters, emoji, control characters. Percent-encoding exists to carry that third tier, and the character map panel on this page labels each character of your input with its tier as you type.

ClassMembersWhat encoding does
unreservedA-Z a-z 0-9 - . _ ~left exactly as they are
reserved: / ? # [ ] @ ! $ & ' ( ) * + , ; =escaped when they appear as data
spacethe space character%20, or + in form mode
percent%%25 — the escape introducer, escaped like anything else
unsafeeverything else, including all non-ASCIIone escape per UTF-8 byte

The percent row deserves its own line because it is the mechanism watching itself. A percent sign in your input can never be emitted bare: %25 is the only way a decoder can tell an intentional percent from the start of an escape. That is also why the character map gives % a class of its own — a % sitting in text you are about to encode is usually a sign that the text was already encoded once, and the map makes it visible before you create a %2520.

The character map lists one row per character, ordered as they appear in the input, with the character, its Unicode code point, its class and its encoded form. Emoji and other characters outside the Basic Multilingual Plane occupy a single row each, because the map counts code points rather than the surrogate pairs JavaScript uses internally to store them. Inputs longer than 500 characters stop the table there and note how many further characters were not listed — the encoding itself is unaffected, only the display is capped.

The double-encoding trap

Double encoding is the most common self-inflicted wound in URL handling, and it follows from one fact: the percent sign is itself a character that must be encoded. Encode 'a b' and you get a%20b. Encode that result again, and the encoder does exactly what it is told — it sees a percent sign that needs protecting and writes %25 in its place, giving a%2520b. The string is now stable under further encoding, but it no longer decodes to what anyone expects: one pass of decoding yields a%20b, the literal characters percent-two-zero, and only a second pass recovers 'a b'.

It creeps in through layers that each look reasonable. A framework percent-encodes query parameters automatically, then a template escapes the URL again before printing it. A developer encodes a value 'to be safe' when it was already encoded upstream. A link is built by concatenating an encoded string into another URL that is itself encoded at the end. Each layer is individually correct; the composition is the bug. The symptom is always the same shape — encoded text appearing where a decoded value should be, a page showing %20, or a search endpoint that literally cannot find the words you typed.

The repair rule is to encode once and late: at the single point where a value leaves your code and enters a URL, using the component-level encoder, and never before. Any string that already contains %20, %2F or %3A should be treated as finished output, not as input to protect again. When you inherit a value that looks over-encoded, this page's Decode tab is the diagnostic: decode it, look at what comes out, and if the result still contains percent escapes, it was encoded twice — decode again and then go fix the producer rather than stacking another decode downstream.

When decoding fails, and what it means

Encoding can succeed on any input, but decoding can genuinely fail, and the failures divide into two families. The first is structural: a percent sign that is not followed by two hexadecimal digits. A string like 100% ends with a percent that has nothing after it, and %2 or %zz start escapes they cannot finish. Strict decoders refuse these rather than guessing, because treating a bare percent as literal text would make the outcome depend on the decoder's mood instead of the data. This page reports the problem in plain language instead of throwing.

The second family is byte-level: escapes that resolve to bytes which are not valid UTF-8. Percent sequences encode bytes, and bytes only spell characters when they follow UTF-8's rules — a lone %C3 promises a two-byte character and then never delivers the second byte, and %FF is not a legal UTF-8 start at all. Sequences like these usually mean the string was encoded from a different character set, Latin-1 being the usual suspect, and the honest answer is that the text cannot be recovered as written; it needs the character set it was actually encoded from.

One quiet trap sits between the families, and it is the plus again. Decode the form-encoded a+b with RFC 3986 mode and nothing fails — the tool returns a+b, technically correct and practically wrong, because the plus you are looking at was a space all along. Nothing flags it, because nothing is malformed. That is why mode choice is a question about the string's origin rather than its contents: malformed input announces itself, while a mode mismatch stays silent until a human notices the spaces that should have been pluses.

Frequently asked questions

Why does '+' turn into a space in one URL and not another?
Because two decoding conventions share the same address bar. The plus-means-space rule comes from HTML form submission — the application/x-www-form-urlencoded format that browsers have used since the 1990s to pack form fields into a query string — and servers apply it only where they expect form data, typically the query. In the path part of a URL a plus is simply itself: /file+name.html reaches the server as file+name.html, never as 'file name.html'. So the mode you decode with has to match where the string came from. A query string assembled by a form or a form-style request library wants form mode; a path, or output produced by JavaScript's encodeURIComponent, wants RFC 3986 mode, where the plus stays a plus.
What's the difference between encodeURI and encodeURIComponent?
They assume you are encoding different things. encodeURI is meant to receive a complete URL and deliberately preserves the characters that give a URL its structure — the scheme's colons, the slashes, ?, #, & and = — while escaping spaces and anything else unsafe. encodeURIComponent is meant to receive one value that will be placed inside a URL, so it escapes all of those structural characters too and only leaves the letters, digits and - _ . ! ~ * ' ( ) bare. Feed a full URL to encodeURIComponent and its :// becomes %3A%2F%2F, which is rarely what you want; feed a bare value to encodeURI and an & inside it survives as a parameter separator that was never intended. This page follows the stricter encodeURIComponent behavior, then goes one step further and escapes the five leftovers ! ' ( ) * as well, because RFC 3986 does not count them as unreserved.
Why do I keep seeing %2520, and how do I undo it?
%2520 is the fingerprint of encoding applied twice. The percent sign itself has to be encoded like any other character — % becomes %25 — so a correctly encoded space, %20, becomes %2520 on the second pass, and 'a b' ends up as a%2520b. You meet it when code encodes at several layers: a framework encodes the parameter, a template encodes it again, or a string that was already a URL gets pushed through an encoder 'for safety'. The tell is a rendered page or a log line that shows %20 as literal text. To undo it, decode twice and see whether the second pass changes anything; to prevent it, encode exactly once, at the last moment before the value is inserted into the URL, and treat any string that already contains %20 or %2F as finished rather than as input.
Why does one emoji become a dozen characters of hex?
Because percent-encoding works on bytes, and an emoji is more than one byte. URLs are transmitted as UTF-8, and UTF-8 gives common characters one byte but rarer ones several: the euro sign is three bytes, and characters beyond the Basic Multilingual Plane — most emoji — are four. Each byte is then written as a percent followed by two hex digits, three characters of output per byte of input. That is why the euro sign becomes %E2%82%AC, nine hex digits across three escapes, and the grinning face becomes %F0%9F%98%80, four escapes totalling eight hex digits. A longer-looking result means more bytes, not a mistake — and decoding reverses it exactly, which is why emoji survive a round trip through this page intact.
Which mode should I pick?
Match the convention of the system that will read your output. Choose RFC 3986 mode when you are building a URL yourself — a path, a query value you are assembling in code, anything a modern library's encodeURIComponent equivalent would produce — because %20 is unambiguous everywhere and a literal plus keeps its plus meaning. Choose form mode when you are reproducing what an HTML form or a query string parser expects: space as +, the convention PHP, ASP and countless backends still apply to submitted data. When decoding something you did not create, let the source decide: a q= parameter that arrived from a search box is usually form-encoded, while a percent-encoded path segment is always RFC 3986. The + test settles it quickly — decode a+b both ways and see which reading matches the value you expected.
Does anything I paste leave my browser?
No. Encoding and decoding here are pure text transformations computed by JavaScript in the page — the same encodeURIComponent and decodeURIComponent primitives your browser exposes to every website — running on the characters you have typed, with no network request attached. There is no endpoint to submit to, no queue, no log; disconnect from the network after the page loads and every part of the tool, including the character map, keeps working exactly as before. That also means the strings you paste — query parameters with tokens in them, links with session identifiers, file names from private systems — are not transmitted anywhere by using it.

Related tools

Last updated: October 9, 2026