The bug that taught me to respect percent-encoding was an OAuth redirect that failed for exactly one customer. WP Adminify had a Google Fonts integration that authenticated through OAuth, and one user โ a hosting reseller running some reverse-proxy setup I still don't fully understand โ kept getting redirect_uri_mismatch errors. Everyone else was fine. I spent the better part of two days blaming his server config. Then I finally looked at the actual URL his browser was sending, character by character, and there it was: %2520 where a space should have been. His proxy was encoding the redirect_uri. My plugin was also encoding it. Google received a URL where the space had been encoded twice โ %20 became %2520 โ and rejected the whole handshake. Two lines of code fixed it. Two days to find it.
That wasn't even my first encoding disaster. Years earlier I'd built a campaign link for a plugin launch with a UTM parameter that contained a raw ampersand โ something like utm_campaign=black&friday. The analytics dashboard showed a mysterious campaign called black and a phantom parameter called friday that matched nothing. The ampersand had silently split my parameter in two. No error. No warning. Just quietly wrong data for eleven days before I noticed the numbers didn't add up.
Here's the thing about URL encoding: it's one of those problems that looks trivial until it isn't. The rules live in a 2005 spec (RFC 3986), the browser reality lives in a different spec (the WHATWG URL Standard), JavaScript gives you three different functions that all do slightly different things, and PHP gives you two more. Get it wrong and you don't get a crash โ you get truncated parameters, broken OAuth flows, and links that work in Chrome but die in an email client.
So I built the encoder/decoder I always wanted into toolz.dev. This guide covers how to use it, and โ more importantly โ how percent-encoding actually works, so the next %2520 in your logs takes you two minutes instead of two days.
TL;DR: To URL encode or decode online, paste your string into the toolz.dev URL Encoder/Decoder, pick a mode, and hit Encode or Decode. It handles UTF-8 and emoji correctly, and the Swap button feeds the output back into the input so you can peel double-encoded values (
%2520) apart one layer at a time. Everything runs client-side, so tokens and session IDs in your URLs never touch a server. Encode parameter values with component mode; only encode full URLs when you know why.
Key Features
Encode and Decode in One Tool
Half the time I need to encode a value. The other half I'm staring at a gnarly URL from a log file and need to decode it into something readable. The tool does both from one input box โ Encode and Decode sit next to each other as two buttons, so there's no hunting for a separate page. Paste an encoded string and hit Decode; type a raw query value and hit Encode. It also round-trips cleanly: encode, decode, and you get your original string back, byte for byte. That sounds obvious, but I've used online tools that mangled plus signs on the round trip because they couldn't decide which spec they were following. This one is explicit about what it's doing at every step, which is exactly what you want when you're debugging.
Component vs Full-URL Encoding Modes
This distinction is where most encoding bugs are born. Component mode encodes everything that isn't unreserved โ including /, ?, &, and = โ which is what you want for a single parameter value. Full-URL mode leaves the structural characters alone so the URL still works as a URL, which is what you want when you're cleaning up a complete address. Using the wrong one either breaks your URL structure or leaves dangerous characters unencoded. The tool puts both modes one click apart in a single dropdown, labelled with the JavaScript function each one corresponds to โ encodeURIComponent, encodeURI, application/x-www-form-urlencoded. I went back and forth on that naming. Intent labels ("encode a value", "encode a whole URL") would read better cold, but function names mean the reference panel underneath maps one-to-one onto the code you're about to write, and that's the moment most people are actually in. If you've ever typed encodeURI when you meant encodeURIComponent โ I have, more than once โ the panel is there to catch it before you ship.
Handles UTF-8, Emoji, and International Characters
Type cafรฉ and you get caf%C3%A9 โ the รฉ correctly expanded into its two UTF-8 bytes. Type an emoji and you get four percent-encoded bytes. This is where older tools and the deprecated JavaScript escape() function fall apart: they assume Latin-1 or produce non-standard %uXXXX sequences that no server can parse. If you're building URLs with user-generated content โ names, search queries, city names in any language that isn't English โ correct UTF-8 handling isn't a nice-to-have. Bengali text, Arabic slugs, Chinese search terms: all of them encode to valid RFC 3986 percent-sequences that decode identically on the other end.
A Swap Button for Double-Encoded Values
The %2520 trap โ an already-encoded %20 getting encoded again โ cost me two days once, so this one is personal. Decoding is a single-layer operation: %2520 decodes to %20, not to a space, because %25 is the encoding of %. One pass gets you one layer. The Swap button (โ) moves the output back into the input box so the next pass is one click away. I've unwrapped URLs three layers deep after they passed through a proxy, a redirect service, and an email link-wrapper โ swap, decode, swap, decode, until the string stops changing. That "stops changing" moment is the actual signal you're looking for. Be honest about what this is: it's a manual loop, not a detector. The tool doesn't flag %25XX for you, and I go back and forth on whether it should โ auto-decoding until stable would be convenient right up until it silently destroys a value that legitimately contained a percent sign.
Three Modes, One Explicit Reference Panel
The mode selector carries three options โ component, full URL, and form-urlencoded โ and the panel underneath spells out exactly which characters that mode escapes, which it preserves, and shows a worked example. I added it because I could never remember whether encodeURIComponent leaves ~ alone (it does) or whether ! and * survive (they do, which surprises people, since RFC 3986 classifies them as sub-delims rather than unreserved). Instead of memorizing three JavaScript functions' quirks, you pick the mode by intent and read back what it's about to do. That reference text is the part I use most, and it's the thing I'd want if I were landing on the page cold at 1 AM.
100% Client-Side โ Nothing Leaves Your Browser
Think about what's actually inside the URLs you decode: OAuth authorization codes, password-reset tokens, session IDs, email addresses in unsubscribe links, API keys some framework helpfully stuffed into a query string. Paste those into a server-side tool and they land in someone's access logs, tied to your IP, retained for who knows how long. The toolz.dev encoder runs entirely in your browser โ the conversion is a few lines of JavaScript executing locally, and no request is made with your data. Open DevTools and watch the network tab if you don't believe me. For anything security-adjacent, client-side isn't a feature, it's the minimum bar.
Free, No Sign-Up, No Limits
No account wall, no daily quota nag for a tool that does string transformation, no "upgrade to Pro to decode more than 1,000 characters." I built toolz.dev because I was tired of ad-choked utility sites that interrupt a ten-second task with a newsletter popup. Bookmark it, use it fifty times a day, done.
How to Use the URL Encoder and Decoder
Step 1: Open the Tool and Pick Your Direction
Go to toolz.dev/tools/url-encoder and drop your string into the left-hand box. There are two action buttons, Encode and Decode, and you pick one after pasting rather than setting a direction first. If you're starting from something readable (a search query, a redirect URL you're about to embed), hit Encode. If you're starting from something full of percent signs (a log entry, a referrer header), hit Decode. Output lands in the right-hand pane with a copy button in its header, and Swap and Clear sit next to the two action buttons.
Step 2: Choose Component, Full-URL, or Form Mode
Encoding a value that will sit inside a parameter โ a redirect_uri, a search term, anything after an = sign? Use component mode. It encodes /, ?, &, and = so your value can't break the surrounding URL. Encoding a complete URL that just needs spaces and non-ASCII characters cleaned up? Use full-URL mode, which preserves the structural characters. The third mode, form-urlencoded, is component encoding with spaces written as + instead of %20 โ pick it when you're hand-building an application/x-www-form-urlencoded body. Note that the mode also affects decoding: in form mode, + is converted back to a space before decoding; in the other two it stays a literal plus. When in doubt: component mode for pieces, full-URL mode for wholes.
Step 3: Read the Output and Watch for Leftover Percent Signs
Output appears in the right pane. For decoding, look at whether the result still contains %XX sequences โ if it does, the value was encoded more than once. Hit Swap to move that output back into the input, decode again, and repeat until the string stops changing. If the input is malformed (a stray % not followed by two hex digits, like a literal 100%), you'll get an explicit error rather than a silent half-decode, which is the behavior you want when you're debugging.
Step 4: Copy and Verify
Hit the copy button and paste the result where it belongs. For anything important โ OAuth redirects especially โ do one final sanity check: paste the encoded value back in decode mode and confirm it round-trips to exactly what you started with. Thirty seconds of verification beats two days of redirect_uri_mismatch.
Percent-Encoding, RFC 3986, and Why Spaces Become Either %20 or +
URLs can only safely contain a limited set of characters. Everything else has to be smuggled in as percent-encoded bytes. The rulebook is RFC 3986 (2005), and it splits characters into two camps.
Unreserved characters never need encoding: the letters AโZ and aโz, digits 0โ9, and four symbols โ hyphen -, period ., underscore _, and tilde ~. Encoding these is legal but pointless.
Reserved characters have structural jobs inside a URL: : / ? # [ ] @ (the general delimiters) and ! $ & ' ( ) * + , ; = (the sub-delimiters). The colon separates scheme from host. The question mark starts the query string. The ampersand separates parameters. Whether a reserved character needs encoding depends entirely on where it appears. A / in the path is structure; a / inside a redirect_uri parameter value is data, and it must become %2F or the server will parse your URL wrong.
The mechanics: take the character, get its UTF-8 byte(s), and write each byte as % followed by two hex digits. ASCII characters are one byte โ space is %20, ampersand is %26. But UTF-8 is a multi-byte encoding, so รฉ is two bytes: %C3%A9. A typical emoji is four bytes โ ๐ encodes as %F0%9F%9A%80. This is why tools that assume one character equals one byte corrupt anything outside plain English.
Now, the space problem โ the single most confusing thing in URL encoding. Per RFC 3986, a space becomes %20. But HTML form submissions use a different serialization, application/x-www-form-urlencoded, defined today in the WHATWG URL Standard, and that format encodes spaces as +. Both are correct โ in their own contexts. Which means + in a query string is ambiguous: it might be a literal plus sign (RFC 3986 reading) or an encoded space (form-encoding reading). If you've ever seen a phone number arrive as 1234 5678 when someone sent +1234..., you've met this bug. My advice: always emit %20 for spaces and %2B for literal plus signs. Nobody misparses those.
JavaScript gives you three functions, and they are not interchangeable. Given the string a=b&c d:
const s = "a=b&c d";
encodeURIComponent(s); // "a%3Db%26c%20d" โ encodes =, &, and space
encodeURI(s); // "a=b&c%20d" โ leaves = and & alone
escape(s); // "a%3Db%26c%20d" โ deprecated; breaks on Unicode
encodeURIComponent encodes everything except unreserved characters (plus !'()* โ a legacy quirk), making it safe for parameter values. encodeURI preserves reserved characters so a full URL stays functional โ but that also means it won't protect an & inside your data. And escape() is deprecated for good reason: it produces non-standard %uXXXX sequences for non-Latin-1 characters. Never use it in new code.
PHP mirrors the same split with a twist: urlencode() produces form-style encoding (spaces become +), while rawurlencode() follows RFC 3986 (spaces become %20). If you're building URLs for anything other than a form POST body, rawurlencode() is the one you want. I shipped WP Adminify code with the wrong one early on; WordPress's own add_query_arg() saved me more times than I'd like to admit.
Finally, the double-encoding trap. %20 is a space, encoded. Encode that string again and the % itself becomes %25, giving you %2520. Decode it once and you get %20 back โ still encoded. This happens whenever two layers of a system each "helpfully" encode: your code plus a proxy, a redirect service plus an email link-wrapper. The rule that prevents it: encode exactly once, at the last possible moment before the value goes into the URL, and never encode something you didn't just decode or generate raw.
Common Use Cases
Building Query Strings with User Input
Any time user-typed text goes into a URL โ search boxes, filters, form values passed via GET โ it must be component-encoded. A user searching for Q&A tips becomes ?q=Q%26A%20tips; unencoded, the server sees a search for Q and a mystery parameter A tips. During development, I use the URL Encoder to generate expected values before writing the code, so I have a known-correct reference to test against. It's also the fastest way to settle "should this character be encoded?" arguments in code review: paste it in component mode and look. Modern APIs like JavaScript's URLSearchParams handle this automatically, and you should use them โ but you still need to verify their output when something breaks, and that's a decoding job.
Debugging UTM and Campaign Links
Marketing links are encoding minefields. UTM values with spaces, pipes, or ampersands; links that pass through a URL shortener, then an email service's click tracker, then a redirect โ each layer a chance for encoding to be added or mangled. When a campaign shows up wrong in analytics, my first move is always the same: paste the full link into decode mode and read what the analytics server actually received. Nine times out of ten the culprit is visible in seconds โ a raw & splitting a parameter, a + that was supposed to be a literal plus, or a %2520 betraying double encoding. My black&friday incident would have been an eleven-second fix instead of an eleven-day data hole if I'd done this on day one.
OAuth redirect_uri and Callback URLs
OAuth is where encoding mistakes get expensive, because providers do exact string matching on redirect URIs. The redirect_uri is a full URL embedded as a parameter value inside another URL โ so it must be component-encoded, exactly once. Under-encode it and the ? or & inside it breaks the outer authorization URL. Double-encode it and the provider compares https%3A%2F%2F... against your registered https://... and returns redirect_uri_mismatch with zero further detail. When that error appears, decode the actual authorization URL from your browser's address bar and compare the redirect_uri character-by-character against your app's registered value. Pair it with the JWT Decoder for inspecting the tokens that come back, and you can debug a whole OAuth flow without leaving the browser.
Decoding Gnarly URLs from Logs and Referrer Headers
Server logs and referrer headers are full of percent-encoded soup: %D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82 in a search referrer, triple-encoded paths from bot traffic, encoded payloads in suspicious requests. Decoding these is how you find out what actually happened โ whether that weird 404 was a user with a Cyrillic query or a script probing for ../../etc/passwd behind three layers of encoding. This is also exactly the situation where the privacy angle matters most: log URLs routinely contain session tokens and email addresses. Decode them in a client-side tool, not on some random server that keeps its own logs. I wrote more about this workflow in the API debugging tools guide.
API Testing with curl
Your shell and curl form a second minefield on top of URL encoding. & backgrounds a process in bash, ? triggers glob expansion in zsh โ so an unquoted, unencoded URL fails in confusing ways before it even reaches the network. My workflow: encode each parameter value in the tool, assemble the URL, wrap it in single quotes, then run curl. When an API returns a 400 for a request that "looks right," I decode the exact URL from the verbose output (curl -v) to see what was really sent โ more than once the bug was my terminal, not my API. curl's --data-urlencode flag handles encoding for POST bodies, but for GET query strings you're mostly on your own, and a reliable encoder beats guessing.
Sharing Links with Non-ASCII Text
Wikipedia articles in other languages, Google Maps links with local place names, docs URLs with Bengali or Arabic slugs โ copy one from your address bar and you may get either the pretty Unicode form or a wall of %E0%A6%AC-style bytes, depending on the browser's mood. Some chat apps and email clients truncate or mangle the raw Unicode form. Encoding the URL before sharing produces a pure-ASCII string that survives every messenger, mailing list, and Markdown renderer I've tried. Going the other way, decoding turns an unreadable shared link back into something a human can verify before clicking โ worth doing before you forward anything that arrived looking like line noise.
encodeURIComponent vs encodeURI vs escape(): Which One Should You Use?
Three functions, one correct default. Here's the honest comparison:
encodeURIComponent() |
encodeURI() |
escape() |
|
|---|---|---|---|
| Encodes | Everything except A-Z a-z 0-9 - . _ ~ ! ' ( ) * |
Everything except unreserved + all reserved chars (: / ? # [ ] @ ! $ & ' ( ) * + , ; =) |
Everything except A-Z a-z 0-9 @ * _ + - . / |
| Space becomes | %20 |
%20 |
%20 |
& and = |
Encoded (%26, %3D) |
Not encoded | Encoded |
| Unicode handling | Correct UTF-8 bytes | Correct UTF-8 bytes | Broken โ non-standard %uXXXX |
| Use for | Parameter values, path segments, anything inside a URL | A complete URL you don't want to restructure | Nothing |
| Status | Standard, recommended | Standard, niche | Deprecated |
My stance, and I'll die on this hill: use encodeURIComponent for values, almost always. The mental model is simple โ if the string is going inside a URL (a query value, a path segment, a redirect_uri), it's a component, and it gets encodeURIComponent. The cases where encodeURI is genuinely right are rare: you have a complete, already-structured URL containing spaces or non-ASCII characters, and you want to sanitize it without touching its structure. That's maybe 5% of real-world encoding calls. And escape() should simply never appear in code written after about 2010 โ its Unicode output isn't valid percent-encoding, and every modern linter will flag it anyway.
One more nuance: encodeURIComponent leaves ! ' ( ) * unencoded for historical reasons, even though RFC 3986 lists them as reserved sub-delimiters. For OAuth and strict-parsing APIs, some libraries add a second pass to encode those five too. If a picky API rejects your values, that's a place to look โ and the URL Encoder's component mode shows you exactly which characters were converted so you can compare.
Frequently Asked Questions
What is URL encoding?
URL encoding (percent-encoding) is the mechanism for representing characters in a URL that would otherwise be unsafe or structurally meaningful. Each problematic character is converted to its UTF-8 bytes, and each byte is written as a percent sign followed by two hexadecimal digits โ a space becomes %20, an ampersand becomes %26. The rules are defined in RFC 3986. It exists because URLs only permit a limited character set, and characters like ? and & have jobs to do inside URL structure.
Why do spaces turn into %20 sometimes and + other times?
Two different specs. RFC 3986, which governs URLs themselves, encodes a space as %20. The application/x-www-form-urlencoded format used by HTML form submissions, defined in the WHATWG URL Standard, encodes a space as +. Both are valid in their own context, which is why + in a query string is ambiguous. The safe practice: always produce %20 for spaces and %2B for literal plus signs โ every parser handles those correctly.
What is the difference between encodeURI and encodeURIComponent?
encodeURIComponent encodes nearly everything, including /, ?, &, and =, making it safe for individual values placed inside a URL. encodeURI preserves those reserved characters so a complete URL keeps its structure. Use encodeURIComponent for parameter values and path segments โ which is almost every real-world case โ and encodeURI only when sanitizing a full URL without restructuring it. Using encodeURI on a value containing & will silently break your query string.
How do I fix a double-encoded URL?
Double encoding happens when an already-encoded string gets encoded again โ %20 becomes %2520 because the % itself turns into %25. To fix it, decode the string repeatedly until no %XX sequences remain and the output stops changing. Then find which layer of your system encoded twice โ commonly your code plus a proxy, redirect service, or email link-wrapper โ and remove one of the encoding steps. The rule: encode exactly once, at the last moment before the value enters the URL.
Is it safe to decode URLs in an online tool?
Only if the tool runs client-side. URLs frequently contain OAuth codes, password-reset tokens, session IDs, and email addresses. A server-side tool receives all of that and may keep it in access logs indefinitely. The toolz.dev URL Encoder/Decoder performs all conversion in your browser with JavaScript โ no data is transmitted anywhere, which you can verify in your browser's network tab. For anything containing credentials or tokens, client-side processing should be non-negotiable.
Do I need to encode the whole URL or just the parameters?
Just the data parts โ individual parameter values and, occasionally, path segments. The structural characters of the URL itself (the :// after the scheme, the ? starting the query, the & between parameters) must stay unencoded or the URL stops working. Encode each value separately with component-style encoding, then assemble the URL around them. Encoding a complete URL end-to-end is only correct when that URL is itself becoming a value inside another URL, like an OAuth redirect_uri.
Can URL encoding handle emoji and non-English characters?
Yes โ modern percent-encoding operates on UTF-8 bytes, so any Unicode character works. A two-byte character like รฉ becomes %C3%A9, and a four-byte emoji becomes four percent-sequences, such as %F0%9F%9A%80. Problems only arise with legacy tools or JavaScript's deprecated escape() function, which assume single-byte encodings and produce invalid output. The toolz.dev encoder handles full UTF-8 correctly in both directions.
Why does my URL break when a parameter contains an ampersand?
Because & is the delimiter between parameters. If a value contains a raw ampersand โ say utm_campaign=black&friday โ the server parses it as a parameter named utm_campaign with value black, plus a second parameter named friday. No error is raised; your data is just silently wrong. Encode the ampersand as %26 inside the value and the parameter arrives intact. This is one of the most common and least visible URL bugs.
What does %2F mean in a URL?
%2F is the percent-encoded forward slash. You'll see it when a value that happens to contain a slash โ a file path, a date like 07/07, or a nested URL โ is correctly encoded before being placed in a query parameter or path segment. Be aware that some servers and proxies (Apache, older Tomcat versions, various API gateways) reject or silently decode %2F in the path for security reasons, so if a request with an encoded slash returns a 404, the server-side configuration is usually the culprit, not your encoding.
How do I URL encode in Python, PHP, or on the command line?
Python: urllib.parse.quote() for path segments and quote_plus() for form-style query values. PHP: rawurlencode() produces RFC 3986 output with %20 for spaces, while urlencode() produces form-style output with +. Command line: jq -rR @uri or curl's --data-urlencode flag. Every one of these matches the component-style behavior of this tool, so you can prototype the encoding here and verify your code produces byte-identical output.
Wrapping Up
URL encoding is a small skill with an outsized payoff. Once you can read %C3%A9 as an รฉ and spot %2520 as a double-encoding smell, a whole category of "it works on my machine" bugs โ broken OAuth flows, phantom UTM campaigns, APIs rejecting perfectly reasonable-looking requests โ turns from mysterious to mechanical. The rules fit on an index card: unreserved characters pass through, everything else becomes UTF-8 bytes as %HH, encode values not structure, and encode exactly once.
Keep the URL Encoder/Decoder bookmarked next to its siblings โ the Base64 Converter for the other encoding scheme you'll meet in every auth header (I've written a full Base64 encoding guide on when to use which), the HTML Entities Encoder/Decoder for the third encoding layer that web content loves to stack on top (the HTML entity guide covers the double-escaping version of the same trap I hit with %2520), and the JSON Formatter for whatever the decoded URL points at.
And if you're assembling a broader browser-based debugging kit, the coding tools guide walks through how these tools fit together in a real workflow. Everything on toolz.dev runs client-side, costs nothing, and does one job well. That's the whole pitch โ the same one I wish someone had made to me before I spent two days on a single misplaced %25.

