URL Encoder
Percent-encode a query string, or pull one apart into its parameters.
Query parameters
Paste a URL with a query in the box above and its parameters appear here, decoded. Editing a row re-encodes it into this string; typing in the box above reads the query back in again.
No query string in the box above yet.
A value going into a query string needs encodeURIComponent , because a & or an = inside it would end the parameter early. A URL going into a link needs encodeURI, which leaves the eleven characters that give a URL its structure exactly where they are. The panes side by side above show which is which without a diagram.
Structure or data, decided by position
Percent-encoding is not a property of a character. It is a decision about what the character is doing in the string it sits in, and the same mark gets a different answer in each slot. Take the ampersand, which is legal in a path, a separator in a query, and data inside a value:
/search/fish&chips the & is part of the path segment
?q=fish&chips two parameters: q=fish, and a key called chips
?q=fish%26chips one parameter, whose value contains "&"
The second line is the whole problem in miniature. Nobody wrote two parameters; the encoder was handed the value fish&chips and left the ampersand alone, and the server did what the grammar says and split there. That is why the encoding is applied to each value on its own rather than to the finished URL: escaping the assembled string would escape the colons and slashes that tell a browser where the host ends, and what comes out is no longer a URL, only a string that looks like one.
Building a query string by hand
A query string is a sequence of pairs separated by ampersands, and the specification says nothing at all about what order they go in. Most servers read them into a map and take the last value for a repeated key, so ?tag=a&tag=b collapses to b, and the list you meant to send is gone. There is no standard for arrays in a query string — tag[]=a, tag[0]=a and tag=a,b are all conventions a particular framework invented, and only the framework that invented one will read it.
That makes repeated keys a question about the endpoint rather than about encoding. If the far end is PHP, tag[]=a&tag[]=b arrives as an array; if it is a plain URLSearchParams on the other side, the same string gives you every value through getAll and the first through get. Sending the pair twice and reading it as a list is the one approach both understand; inventing bracket syntax is the approach that works until the endpoint changes.
The other half of building by hand is what happens to keys nobody meant to send. Any raw & or = inside a value adds a parameter, and a parameter that arrives unannounced is worth treating as hostile rather than as noise: the server sees a key it did not expect, and what it does with an unexpected key depends entirely on how carefully it was written.
The fragment is a browser place
Everything from a # onwards is the fragment, and the browser keeps it. It is not sent in the request, it does not appear in a server log, and no amount of correct encoding will change that — a parameter written after the hash is not malformed, it is never transmitted. The address bar shows it, the page's own scripts can read it, and the network panel shows a request without it, which is why this one takes an afternoon to find rather than a minute.
Two habits avoid it. Put parameters that the server needs before the hash, always. And when the hash genuinely belongs to the value rather than to the URL — a redirect target that carries its own anchor, a colour written #ff8800 in a form field — escape it as %23 so the browser stops reading it as structure.
Telling whether a string was encoded twice
Encoding is not idempotent, and that is the property to test rather than to reason about. Decode a string once and compare it to the original: if the result still contains percent escapes, the string had been through the encoder before. The signature is a %25, which is an escaped percent sign, so %2520 decodes in two passes to a space and in one pass to the four characters %20.
Nothing errors when this happens, which is what makes it worth a deliberate check. The string stays valid, the server receives exactly the characters written, and the fault surfaces later as a stray percent sign in a search box or a filename with %20 in it. When you find one, look for the second pass rather than adding a third — and if the value has to cross a channel that only carries text afterwards, Base64 Encoder is the other way to do that, chosen for different reasons.
Reference
The characters that mean something, and where they mean it
| Character | In a path | In a query string | In a value | Why it matters |
|---|---|---|---|---|
| space | %20, since a raw space is not legal in a path | + or %20, depending on who wrote the encoder | %20, when the space is part of a key=value pair | An encoder that writes + for a space is the usual reason a space turns into a literal plus somewhere downstream. |
| + | a literal plus, and always has been | a space, under form rules | %2B when the plus is data | A plus means a space in a query and a plus in a path, the most confusing asymmetry in the subject. |
| & | legal, and separates nothing | starts a new parameter | %26 | Left raw in a value, it starts a parameter nobody meant to send and the value is truncated at that point. |
| = | legal | splits the key from the value | %3D | Milder than the ampersand, because parsers split the pair at the equals sign and most values survive whole — encode it anyway, since some readers split at the other end. |
| # | %23 | %23 | %23 | Raw, it starts the fragment, and everything after it never reaches the server. |
| % | %25 | %25 | %25 | An unescaped percent followed by a pair of hex-looking characters decodes to a byte, or the decoder gives up on the string. |
| / | the separator between segments | legal, and common inside a value | %2F | A server that decodes the path before it picks a route can turn an escaped slash back into a boundary. |
| ? | %3F | legal, where it is only data | %3F | Raw in a path or a value, it starts the query and the rest of the string is read as parameters. |
| é | %C3%A9 | %C3%A9 | %C3%A9 | Multi-byte in UTF-8 rather than a single code point; an old encoder writing %E9 is a real source of mojibake. |
| a whole URL | not data, since its slashes would read as segment boundaries | encodeURIComponent, over the whole string | every separator inside it is escaped, including its own ?, = and & | This is what a redirect parameter expects; left raw, the inner URL splits the outer query. |
Every row asks the same question of a character — is it data here, or structure — and gets a different answer depending on the column. The escape shown in the middle columns is what settles the argument in favour of data.
Read the table as a set of rules about position rather than about characters. The same mark can be ordinary data in one slot and a separator in another, which is why the encoding is applied to each value on its own rather than to the finished URL. Encode the whole string and the colons and slashes that tell a browser where the host ends get escaped along with everything else, and what comes out is no longer a URL.
The percent row is where most of the damage happens. Every pass over an already-encoded value escapes the percent signs the previous pass wrote, so %20 turns into %2520 and a space is quietly replaced by the text of its own escape. Nothing complains when this happens: the string stays valid, the server receives those characters as written, and the fault surfaces later as a stray percent sign in a search box. When a value turns up carrying %25, find the pass that added it rather than adding another.
The fragment is a browser-only place. A # ends the part of the URL that is sent, so a parameter written after one never reaches the server, however correct the encoding looks in the address bar. That is a real and frequent afternoon of debugging. If a value has to travel, it belongs before the hash.
Questions
Escapes, answered plainly
Which of the two functions do I want?
Ask what the string is. If it is a value that will sit inside a query string — a search term, an email address, a redirect target — use encodeURIComponent, so that its own &, =, ? and # cannot end the parameter early. If it is a complete URL that a browser will follow, use encodeURI, or you will hand it https%3A%2F%2Fexample.com%2F and it will treat the whole thing as a path. The panes above show both answers at once so you can see which one kept the slashes.
Why did my space turn into a plus sign?
Because the parameter builder encodes the way a form submission does. In application/x-www-form-urlencoded a space is +, while the two JavaScript functions write %20; servers accept either when reading a query string. The difference only bites in the other direction, when something decodes a + in a value that was never a form field — a path segment, for instance, where a literal plus stays a plus.
What is %25 doing in my URL?
It is an encoded percent sign, and it nearly always means the string was encoded twice. %20 is a space; encode that string again and the percent sign becomes %25, giving %2520, which decodes back to the four characters %20 rather than to a space. The remedy is to find where the second encoding happens and remove it, not to add a third pass — the button above exists so you can count how many passes a string has had before you go looking.
Can a whole URL go inside a query parameter?
Yes, and it has to go in through encodeURIComponent. A redirect parameter of ?next=https://example.com/a?b=1&c=2 is two parameters, not one, because of the & the inner URL brought with it; encoded, the inner separators become %3F, %3D and %26 and the outer URL keeps its shape. Those escapes are also what stops a parameter from quietly adding a key a server was not expecting, which matters more than the tidiness.
Is the address I paste here requested?
Not by this page, and not by anything it talks to, because it talks to nothing. The string is read as characters and rewritten as characters: it is never resolved, never looked up and never checked against a live host, so pasting an internal address reveals nothing to anyone. That is also why the tool cannot tell you whether the URL works — it has no way of knowing, and no interest in finding out.
Why is my parameter value cut off?
Almost always an unescaped & inside the value. A query of ?q=fish&chips arrives at the server as a query holding an extra key, so the value it reports stops at the ampersand and the rest looks like a parameter nobody sent. Put the value through encodeURIComponent before it goes into the query and the ampersand travels as %26. A raw # cuts a value off in the same way, and more completely, because it ends the URL rather than the parameter.
Why does the server not see my parameter?
Because it sits after the #. Everything from the hash onwards is the fragment, and the browser keeps it for scrolling and for the page's own scripts without ever putting it in the request. Move the parameter in front of the hash, and if the hash genuinely belongs to the value, escape it as %23 so the browser stops reading it as a fragment. The network panel shows nothing either way, which is why this one takes so long to spot.
Why does my link work in one browser and not another?
Some browsers quietly repair a URL that a server would turn down: a raw space, a { or a | gets escaped on the way out, and the request leaves clean. Open the same link from a stricter client and the character arrives as itself, where a proxy or the server rejects it. The address bar is not a validator, and the fact that it worked yesterday is the trap. Encode the character properly and both browsers send the same bytes.