URL Encoder/Decoder
Percent-encode or decode URLs, query strings and Unicode. The page below compares what encodeURIComponent, URLSearchParams and Python's quote and quote_plus actually produce, because all four disagree.

Output will appear hereEncode URLs and query values for safe transmission, or decode percent-encoded text back to something readable. Handles full URLs, query parameters, and Unicode. The page below shows why four standard-library encoders give four different answers for the same input, which is where most encoding bugs come from.
What This Does, And Who It Is For
Percent encoding replaces a character with a % followed by two hex digits representing its bytes in UTF-8. A space becomes %20, an ampersand becomes %26. The rules come from RFC 3986, the URI generic syntax standard published in January 2005 and still current.
Paste a string in, get the encoded form out, or paste an encoded string in and get it back. The tool encodes as a component, meaning it assumes what you gave it is a single value going into one slot of a URL rather than a whole URL, which is the case that causes trouble.

It is for anyone hand-building a URL: wiring an HTTP node in n8n or Make, debugging a redirect chain that has lost a parameter, working out why an OAuth callback is rejected, or reading a log line full of % sequences. If you are writing application code, use your language's own encoder rather than pasting output from a web page. The value of this page is the next section, which is about what your language's own encoder actually does.
The Character Sets, From The Spec
RFC 3986 divides characters into three groups. Section 2.3 defines the unreserved set, which never needs encoding:
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"Section 2.2 defines the reserved set, which is split in two:
reserved = gen-delims / sub-delims
gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/ "*" / "+" / "," / ";" / "="Reserved characters are the ones that mean something structurally. Whether they need encoding depends on which slot the value is going into. A / inside a path segment must be encoded, because otherwise it creates a new segment. The same / in a query value usually does not need to be, because nothing in the query parses it. Everything not in either list, including every non-ASCII character, is encoded unconditionally.
One line in section 2.3 is worth keeping in mind because it constrains encoders rather than just users. Percent-encoded forms of the unreserved characters, listing the ranges for letters, digits, hyphen, period, underscore and tilde, "should not be created by URI producers." An encoder that emits %7E for a tilde is producing something the spec tells it not to. Hold that thought for two sections' time.
Worked Example: Four Encoders, Four Different Answers
This is the part every URL encoder page skips, and it is the only part that will save you an afternoon.
Take one deliberately awkward fifteen-character string containing a space, a plus, an ampersand, an equals sign, and the five sub-delims that implementations disagree about:
a b+c&d=e!*'()~Run it through four encoders from the standard libraries of the two languages this sort of work usually happens in. These are measured outputs, run on Node 22 and Python 3.10, not descriptions of what should happen:
| Encoder | Output |
|---|---|
JavaScript encodeURIComponent() |
a%20b%2Bc%26d%3De!*'()~ |
JavaScript URLSearchParams |
a+b%2Bc%26d%3De%21*%27%28%29%7E |
Python urllib.parse.quote(s, safe='') |
a%20b%2Bc%26d%3De%21%2A%27%28%29~ |
Python urllib.parse.quote_plus(s) |
a+b%2Bc%26d%3De%21%2A%27%28%29~ |
Four encoders, four distinct strings, all correct by their own rules. Three characters do the damage:
The space. encodeURIComponent and quote give %20. URLSearchParams and quote_plus give +. Both are right in context: the +-for-space convention comes from the application/x-www-form-urlencoded serialiser, which is a form-submission format rather than a URI rule. MDN's encodeURIComponent reference states it directly: for application/x-www-form-urlencoded, "spaces are to be replaced by +, so one may wish to follow an encodeURIComponent() replacement with an additional replacement of %20 with +." Mix the two and a receiver that decodes form-style turns your literal plus signs into spaces.
The asterisk. Three implementations, three behaviours. encodeURIComponent leaves it bare. URLSearchParams also leaves it bare. Python's quote encodes it as %2A. One character, and you cannot predict it from the spec, because * is a sub-delim with, as MDN puts it, "no formalized URI delimiting uses."
The tilde. encodeURIComponent and quote leave it alone, which is what section 2.3 asks for. URLSearchParams emits %7E, which is the case the spec says producers should not create. It is harmless in practice, since a normaliser will decode it back, but it means two encoders in the same runtime produce different bytes for an unreserved character.
The wider pattern behind all three: encodeURIComponent escapes everything except A-Z a-z 0-9 - _ . ! ~ * ' ( ), per MDN. Compared against the RFC's unreserved set, that is five extra characters left bare, exactly !, ', (, ) and *. Python's quote with safe='' matches the RFC's unreserved set precisely, and encodes none of it. So the two languages disagree on five characters by design, and neither is wrong.
The Two Rules That Follow
Encode once, at the boundary, and never in a loop. Double encoding is the single most common URL bug and it is trivially diagnosable: look for %25, the encoding of % itself. A space that has been through the encoder twice reads %2520. Once you see %25 in a URL that should not contain a literal percent sign, you know the value passed through two encoders, and the fix is to remove one rather than to add a decode.
Encode the value, not the URL. encodeURIComponent applied to an entire URL destroys it, turning https:// into https%3A%2F%2F. That form is correct only when the URL is itself a value, as in ?redirect=https%3A%2F%2Fexample.com%2Fpath. JavaScript's encodeURI exists for the other case and deliberately leaves the structural characters alone: measured, it preserves ! # $ & ' ( ) * + , - . / : ; = ? @ _ ~ along with letters and digits. Using encodeURI on a user-supplied value is the mirror-image mistake, because it will happily leave an & in place and let your parameter split in two.
Common Mistakes
Assuming the receiver decodes the way you encoded. Query strings are decoded by whatever framework is on the other end, and frameworks differ on +, on repeated keys, and on empty values. If you control both ends, pick one convention and write it down. If you do not, test against the real endpoint rather than the docs.
Encoding the delimiter you needed. If you encode the & between two parameters, you no longer have two parameters, you have one parameter with a long value. Encode the values; assemble the string afterwards.
Building query strings by concatenation. Almost every encoding bug is really a string-building bug. Use URLSearchParams or your language's equivalent, hand it key and value pairs, and let it do the escaping. Then read the output once to confirm which convention it chose.
Forgetting that non-ASCII is multi-byte. Percent encoding operates on bytes, not characters, so a character outside ASCII becomes several triplets. The pound sign becomes %C2%A3, three bytes of emoji become four triplets. If you are counting characters against a length limit somewhere downstream, count the encoded length.
Passing a lone surrogate to a JavaScript encoder. Half a surrogate pair, which is what you get from a careless slice on a string containing an emoji, throws rather than degrading. Measured on Node 22, encodeURIComponent("\uD800") raises URIError: URI malformed. MDN suggests String.prototype.toWellFormed() to replace lone surrogates with U+FFFD before encoding, or isWellFormed() to check first.
Trusting a decoded string. Decoding is where injection payloads arrive already assembled. A percent-encoded <script> is inert in a log file and live once it has been decoded into a page. Decode late, escape for the output context, and never treat a decoded value as safe because it looked harmless encoded.
Frequently Asked Questions
Should a space be %20 or +?
%20 unless you are producing application/x-www-form-urlencoded, in which case +. Inside a path, always %20, because + in a path is a literal plus. Inside a query string either can be decoded correctly by a well-behaved receiver, but they are not interchangeable if the receiver only implements one.
Why does my plus sign disappear?
Because something on the receiving end decoded form-style and read your literal + as a space. Encode a literal plus as %2B and the ambiguity goes away regardless of which convention the other end uses. This accounts for a large share of broken phone numbers in webhook payloads.
What is the difference between encodeURI and encodeURIComponent?
encodeURI is for an entire URL and leaves the structural characters intact, so the result is still a usable URL. encodeURIComponent is for one value going into one slot and escapes the structural characters too. If you are unsure which you want, you almost always want encodeURIComponent, because the case where you have a whole URL that needs encoding is rare.
Do I need to encode a slash in a query parameter?
Usually not. A / in a query value is not parsed as a delimiter and most receivers accept it bare, which is why Python's quote leaves it alone by default and you have to pass safe='' to change that. Encode it if the value will later be pulled out and used as a path segment, or if some proxy in between normalises paths.
Why does the same input give different output in JavaScript and Python?
Because the two standard libraries chose different sets of characters to leave bare, and the spec permits both. The table above shows the exact difference: five sub-delims, !, ', (, ) and *. Both outputs decode back to the same string, so this only becomes a bug when something compares encoded forms rather than decoded values, which signature verification and cache keys both do.
Is a percent-encoded URL the same URL?
For unreserved characters, yes. RFC 3986 says URIs differing only by the percent-encoded form of an unreserved character are equivalent and identify the same resource. For reserved characters, no: %2F and / are genuinely different, which is the whole reason for encoding them.
When The Encoder Is Not The Problem
If you are on this page, one string is probably not your actual problem. The bugs that cost real time live at the seams, where a value gets encoded by a form, re-encoded by a framework, stored, read back, and interpolated into a URL by hand. Every layer is behaving correctly on its own and the result is %2520 in a redirect at eleven at night.
Finding those requires reading the whole path a value takes rather than testing the ends of it, and then usually deleting a layer rather than adding a decode. That is most of what I do on the build side: taking something that mostly works, tracing where the data actually goes, and making the seams explicit. Sometimes that is a webhook integration that has been patched four times, sometimes it is a scenario in n8n or Make that grew past what a visual editor can hold, and sometimes it is a rough prototype that needs to become something you can hand to a client without a list of caveats.
For the layer either side of encoding, the API tester will confirm whether the request works before you wire it into a live scenario, and the JWT decoder covers the other format that arrives base64 and leaves you guessing. Make.com's HTTP module walks through where this comes up in practice, with the encoding decisions called out where they matter.
If you have a URL that only breaks in production, send me the failing case rather than the code. That is usually enough to spot which layer is encoding twice.

Want this built against your real numbers?
A 30-minute call to scope the workflow, agent, or automation you actually need.
More developer tools
All tools
.env Manager
Validate environment variable names and values before they reach production. Env Manager checks names against POSIX rules, flags values that different .env parsers read differently, and shows which entries are secrets that should not sit in a file.

.gitignore Generator
Build a .gitignore for your stack in seconds. Covers dependencies, build output, IDE files and the .env patterns that keep secrets out of a public repository.

API Mock Server
Create a live mock REST endpoint with your own path, method, status code, headers, delay and JSON body — so you can build and test a frontend or automation before the real API is ready.

API Request Tester
Send REST API requests from your browser with custom headers, auth and a JSON body, and inspect the status, headers and response. Includes a guide to reading status codes and diagnosing CORS.

Base64 Encoder
Convert text, JSON or files to Base64 in the browser, with nothing sent to a server. Covers the padding rules, the exact 33% size increase, and the non-canonical strings that decode to identical bytes.

Cron Expression Generator
Build cron expressions visually and get the correct string for crontab, GitHub Actions, EventBridge, Kubernetes, Make or n8n — with a field reference and the common gotchas explained.
Have a workflow that's burning hours every week?
Bring me one real bottleneck. I'll tell you whether it's worth automating, and what it would take.