URL Encoder/Decoder
Percent-encode and decode URLs with the encodeURIComponent vs encodeURI difference made explicit — entirely on this device.
+ is kept as a literal plus in Component mode — %20 is the space.
Query parameters appear here when the input contains a query string.
Press ? for shortcuts
Do it offline:$ python3 -c 'import sys,urllib.parse; print(urllib.parse.unquote(sys.argv[1]))' 'caf%C3%A9%20%2B%20cr%C3%A8me'
How to use it
- Paste your string — a %XX escape anywhere preselects the decode direction, the badge names what was detected, and one click (or the s key) reverses it.
- Pick the mode that matches where the text lives: Component for a value going inside a URL, Full URL for a complete address whose :/?&=# skeleton must survive, Form for form-submission data where + stands for a space. Each option states its consequence right next to the label.
- If the input carries a query string, work on it as a table: edit a value, add or remove rows, and take the result from the rebuilt-URL preview and its own copy button — your pasted input is never rewritten underneath you.
- Copy or download the converted text, or use Share to fold the current state into a link. When the input is shaped like a credential, a confirmation explains what the link would carry before one is made.
Paste a mangled query string, a value bound for a URL, or a whole address, and the conversion runs as you type — in the mode the job actually calls for: Component, Full URL or Form. Query strings unfold into a table of editable key/value rows that rebuilds the URL live, anything the decoder tolerated is declared as a chip, and none of it ever leaves this device.
How it works
RFC 3986 allows a URL to carry only a small repertoire of characters, so everything else travels as percent-encoding: the character is converted to bytes — UTF-8 on the modern web — and each byte outside the allowed set is written as % followed by two hex digits of its value. One character can therefore become several escapes: £ is the two bytes C2 A3, printed as %C2%A3. The hex digits are uppercase in canonical form (RFC 3986 §2.1); lowercase arrives constantly in the wild, so the decoder here accepts it and says so rather than failing or fixing it silently.
What separates the modes is which characters get escaped. RFC 3986 splits the repertoire into unreserved characters, which never need encoding, and reserved ones — : / ? # & = + and their kin — which are the URL's own punctuation. JavaScript ships both answers: encodeURIComponent escapes the reserved set, correct for a value being placed inside a URL, while encodeURI leaves it alone, correct for tidying a complete address. Run the wrong one and you either break the URL's structure or smuggle live delimiters into your data. This page turns that invisible choice into the Component and Full URL modes, and the decode side mirrors it: Full URL leaves escaped delimiters like %2F encoded, because resolving them would change which resource the address points at.
Form mode answers to a different standard altogether. application/x-www-form-urlencoded — HTML form serialization, now specified by the WHATWG URL Standard — predates RFC 3986 and writes a space as + instead of %20. That single divergence is behind most plus-sign bugs: a + is a literal plus under RFC 3986 but a space in a form payload. Decoding in Form mode therefore turns every + into a space before resolving escapes, and the note under the output states how the active mode is reading + at that moment.
Decoding itself never leans on the browser's decodeURIComponent, which throws a URIError with no position attached. A hand-rolled scanner walks the input instead, so a malformed or cut-off escape is reported at its exact 1-based position while everything else stands. The resolved bytes are then validated as UTF-8; when they fail — the signature of a legacy server that escaped raw Latin-1 bytes — the page offers a Latin-1 reading as an explicit choice instead of printing mojibake or guessing on your behalf.
Examples
fish & chips = £3.50fish%20%26%20chips%20%3D%20%C2%A33.50Component mode, the default. Spaces become %20, the & and = are escaped because inside a query value they would read as delimiters, and £ spends two escapes — one per UTF-8 byte.
https://example.com/search?q=fish & chipshttps://example.com/search?q=fish%20&%20chipsThe teaching pair. Full URL mode repairs the illegal spaces and leaves the address's structure standing. Component mode applied to the same input produces https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dfish%20%26%20chips — a faithful value, but no longer a clickable URL. Choosing between those two outputs is the entire encodeURI vs encodeURIComponent question.
caf%C3%A9%20%2B%20cr%C3%A8mecafé + crèmeDecoding in Component mode: %C3%A9 and %C3%A8 reassemble into é and è through UTF-8, %2B yields a real plus, and %20 the spaces. Form mode reads this particular input identically, because the plus is escaped rather than literal.
fish+%26+chipsfish & chipsThe + ambiguity, executed. In Form mode each + is a space and %26 is the ampersand — exactly how a browser serializes a form field. Component mode keeps the pluses literal and returns fish+&+chips instead, which is why a decoder has to know where its input came from.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
A + only means a space in form payloads
Under RFC 3986 a plus is just a plus; in application/x-www-form-urlencoded it is the space. The same characters therefore decode two ways, and no tool can tell you which is right without knowing the source. Here the mode makes the choice explicit, the line under the output states the current reading, and switching modes re-runs everything instantly — paste once, compare both.
Double-encoding leaves a fingerprint: %25
Encode an already-encoded string and every % becomes %25 — %20 turns into %2520. Pipelines do this when two layers each apply their own escaping. When one decode pass still leaves valid escapes behind, the page offers a second pass with an undoable banner; encoding input that already contains escapes earns a warning chip before you mint a third layer.
Lowercase escapes are tolerated, declared, and strict-checked
%c3%a9 decodes the same as %C3%A9, but only the uppercase form is canonical under RFC 3986. The tolerant default accepts lowercase and surfaces a chip naming the deviation; strict mode turns the same escapes into position-anchored errors — the setting to use when validating what a spec-exact consumer will accept rather than recovering your own data.
Escaped delimiters are data — Full URL decode leaves them be
A %2F inside a path segment is a slash that belongs to the data, deliberately escaped so it does not read as a path separator. Resolving it would silently point the URL somewhere else. Full URL mode's decode therefore keeps escaped delimiters encoded while resolving everything harmless; Component mode, meant for extracted values, resolves them all.
Internationalized hosts do not percent-encode
Percent escapes belong to paths, queries and fragments. Hostnames use a different mechanism entirely: Punycode, the xn-- prefix notation of internationalized domain names. When a pasted URL carries such a host, this page points it out in plain prose — decoding the rest of the URL neither converts nor breaks the hostname.
Not UTF-8? The bytes get a second, honest reading
Servers from the pre-UTF-8 era escaped raw Latin-1, so %E9 meant é as a single byte — which is not valid UTF-8 and would render as garbage if forced. When decoded bytes fail UTF-8 validation, the output offers a Latin-1 reinterpretation as a labeled choice you can flip back, and whatever is copied or downloaded always carries the true bytes of the reading you chose.
Past 1 MB the page declines the whole input
Real URLs live in kilobytes; a megabyte of them is a log file, and past the cap a banner names the limit and the page works on none of it — a shortened version would be a different string with different positions. Unlike Base64 or hex, coreutils ships no URL decoder, so the honest command line is Python's urllib (shown under the tool); jq's @uri filter covers the encode direction.
Common errors
Invalid sequence `%G1` at position 23- A % must be followed by two hex digits, and G is not one. The position counts from one across exactly what you pasted, and the usual culprits are a printf-style placeholder, a truncated escape glued to the next character, or text that was never percent-encoded to begin with. Your paste is left as-is while you repair it.
Truncated sequence `%C` at position 41 — a percent escape needs two hex digits- The input ends in the middle of an escape — one % and at most one digit, where two are required. Almost always a clipped copy: a log line cut at a column limit, or a URL split across lines by an email client. Recover the missing tail rather than deleting the fragment.
Input is larger than the 1 MB limit for this tool. For bigger payloads use the CLI: python3 -c 'import sys,urllib.parse; print(urllib.parse.unquote(sys.stdin.read()))' < encoded.txt- The banner declines the whole input rather than operating on a slice of it, and what you pasted is left intact. Anything over the cap is realistically a log or a dataset, and streaming it through Python's urllib on your own machine keeps it just as private as this page does.
FAQ
When should I use encodeURIComponent instead of encodeURI?
Ask what the string is. A value headed into a URL — a search term, a redirect target, a UTM field — needs encodeURIComponent, which is Component mode here: every delimiter gets escaped so the value cannot alter the URL around it. A complete address you are cleaning up needs encodeURI, Full URL mode: the punctuation that makes it a URL stays functional. The second example above shows both outputs for one input.
%20 or + — which one is the space?
Both, in different places. %20 is the space everywhere and is the only correct form in paths and fragments. + means a space only inside application/x-www-form-urlencoded data — traditional form submissions and many query strings. When in doubt, emit %20 and accept both on the way in, which is precisely what Component encoding and Form decoding do on this page.
Why does my string need decoding twice?
Because it was encoded twice. Somewhere a component received already-encoded text and escaped it again — a gateway re-encoding a forwarded URL, a template escaping a variable that arrived escaped. The tell is %25, the escape of % itself: %2520 is %20 wearing a second coat. This page notices when one pass leaves valid escapes behind and offers the next pass as an undoable action.
Is URL encoding a form of encryption?
No. It is a reversible transport notation with no key and no secret — this page reversed yours without being told anything. A token or password inside a query string is plainly readable by every log, proxy and browser-history entry it passes through, and should be treated as exposed the moment it appears in a URL.
Which characters have to be percent-encoded in a URL?
Everything outside RFC 3986's unreserved set — letters, digits, hyphen, period, underscore and tilde — needs encoding in at least some position. The reserved delimiters : / ? # & = + and friends are the subtle part: legal as structure, escaped whenever they occur inside a value. That context-dependence is what the mode selector captures. One inherited quirk: Component mode, true to encodeURIComponent, lets ! * ' ( ) through unescaped — worth knowing when a strict consumer refuses them.
Why does é come out as é — or refuse to decode at all?
Character-set mismatch. é is UTF-8's two bytes for é being read as Latin-1 by some other tool; the reverse case — a legacy server's single-byte %E9 — is not valid UTF-8, so this page stops and offers a Latin-1 reinterpretation instead of printing the wrong answer. One honest question beats a silent guess in either direction.
Can I edit query parameters without hand-splicing the URL?
Yes — that table below the panes is live. Paste an analytics URL and each parameter appears as a decoded, editable key/value row, duplicates preserved in order. Change a value, add or remove rows, and copy the rebuilt URL from its preview; the rebuild re-encodes per the active mode, so + versus %20 comes out however your target system expects.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Force the conversion to run again right now, at any input size.
- ⌘/Ctrl + S
- Save the output to a .txt file — on this page it downloads rather than saving the page, as the help overlay warns.
- ⌘/Ctrl + C
- Copy the entire output while focus is in the tool and nothing is selected; an active selection copies normally, just itself.
- ⌘/Ctrl + Shift + C
- Copy the full output no matter what is selected.
- s
- Reverse encode and decode (ignored while typing in any field, during IME composition, and when single keys are disabled).
- t
- Step to the next color theme (same single-key rules).
- ? or ⌘/Ctrl + /
- Open keyboard help, where browser-shortcut conflicts and the param-table keys — Enter in the last row adds one — are documented.
- Esc
- Close whichever dialog is on top; the input is never cleared.