URL encoder / decoder

Percent-encode and decode URLs, choose component or full-URL scope, and see a URL split into its parts.

For a single value such as one query parameter. Escapes / ? : @ & = + too, because there they are data rather than structure.

Input
Output

Output will appear here

What percent-encoding is for

A URL is allowed to contain only a small set of characters: the letters A–Z and a–z, the digits, a handful of marks like - . _ ~, and a set of reserved characters that give the address its shape — / ? # & = : @ and a few more. Anything else, from a space to a Hebrew word to an emoji, has to be represented indirectly. Percent-encoding is that representation: the 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, and א becomes %D7%90.

The reserved characters are the interesting case. They are legal in a URL, but they mean something structural: ? starts the query, & separates parameters, = splits a key from its value. When one of those characters is part of your data rather than part of the structure, it must be encoded — otherwise the parser on the other end will read it as punctuation and split your value in the wrong place.

Component or full URL — the distinction that breaks links

This is the single most common source of URL bugs, and it is why this tool asks you to choose a scope rather than guessing.

  • Component scope escapes the delimiters as well: / ? : @ & = + $ , and #. Use it for one piece of data — a search term, a redirect target, a token — that is being placed inside a larger URL.
  • Full-URL scope leaves those delimiters alone and escapes only what is genuinely illegal, such as spaces and non-ASCII text. Use it when you have a complete address that is already structured correctly and merely needs cleaning up.

Get it backwards in either direction and something breaks. Encode a whole URL in component scope and every slash and question mark turns into %2F and %3F, producing one long unusable string. Encode a single value in full-URL scope and an ampersand inside it survives as a delimiter, so a search for "cats & dogs" silently becomes two parameters and the second half of your value disappears.

// The value contains a delimiter, so it must be escaped:
const q = 'cats & dogs'
`/search?q=${encodeURIComponent(q)}`  // /search?q=cats%20%26%20dogs
`/search?q=${encodeURI(q)}`           // /search?q=cats%20&%20dogs  ✗

// Better still, let the platform assemble it:
const url = new URL('https://example.com/search')
url.searchParams.set('q', 'cats & dogs')

That last approach is worth adopting as a habit. URL and URLSearchParams are built into every browser and into Node, and they apply the right encoding to each part for you — which removes the decision entirely rather than making you get it right by hand every time.

Why a space is sometimes + and sometimes %20

Two different specifications are at work, and they disagree about exactly one character. RFC 3986, which governs URLs generally, encodes a space as %20. The older application/x-www-form-urlencoded format, which is what HTML forms submit and what most query strings therefore look like, encodes a space as +.

The trap is that decodeURIComponent implements only the first rule. Hand it hello+world and you get hello+world back, plus and all — no error, no warning, just a value that is subtly wrong. That is why this tool offers a "treat + as space" option and points it out when your input contains a plus while the option is off.

Note that URLSearchParams, which powers the breakdown table below the tool, follows the form rules and does convert + to a space. So the same query string can decode differently depending on which API you reach for — deliberately, and correctly, in both cases.

One consequence worth remembering: if a plus is genuinely part of your data — a phone number, a filter expression — it must be encoded as %2B. A literal + in a query string is ambiguous at best and will be read as a space by most parsers.

Reading a URL

Paste an absolute URL and the tool splits it into its parts. It is worth knowing what each one is called, because error messages and documentation assume the vocabulary:

  • Scheme — https, mailto, ftp. Everything before the colon, and the thing that decides how the rest is interpreted.
  • User info — an optional user:password before the host. Still legal, and still a bad idea: it travels with every request and lands in logs, browser history and referrer headers.
  • Host — the domain name or IP address. Everything after it is handled by that server, not by the network.
  • Port — usually absent, because 443 for https and 80 for http are implied.
  • Origin — scheme, host and port together. This is the unit that browser security is built on: same-origin policy, CORS and cookie scope all compare origins, not paths.
  • Path — the part after the host and before any ? or #.
  • Query — the key/value pairs after the ?, separated by &.
  • Fragment — everything after the #. Uniquely, this is never sent to the server; it is handled entirely by the browser.

The fragment detail matters more than it sounds. Because it never leaves the browser, anything you put after a # is invisible to server logs — which is why some single-page apps historically used it for routing, and why it is not a place to look when debugging a request that never arrived.

A final caution about what encoding does not do. Percent-encoding makes text safe to transport inside a URL; it is not sanitisation and it is not a security control. Encoding a value does not make it safe to interpolate into HTML, SQL or a shell command, and decoding untrusted input can reveal characters — path separators, null bytes — that the encoded form was hiding. Validate what a value is, separately from encoding how it travels.

The address you typed is not always the address that is sent

Before a URL goes anywhere it is normalised, and the rewriting is silent. This tool shows the result whenever it differs from the input, because that string — not the one you typed — is what reaches the server and what appears in a log:

HTTPS://Example.COM:443\a\b     typed
https://example.com/a/b         sent

Four separate rules fired there. The scheme and host are lowercased, since neither is case-sensitive. The port is dropped because 443 is the default for https. The backslashes become forward slashes, a compatibility rule that surprises people writing Windows-style paths. And an empty path would have become a single slash. None of this is an error, but if you are comparing two URLs for equality, or matching one against an allow-list, you have to compare the normalised forms or you will get the wrong answer.

A hostname with non-ASCII characters is rewritten too, into an ASCII form beginning xn--, called punycode. The tool shows both directions: the readable name and the one that actually travels. This is worth looking at rather than skipping, because two different Unicode names can look identical on screen — a Latin "a" and a Cyrillic "а" are separate characters — while producing completely different punycode. Comparing the ASCII forms is the only reliable way to tell such a pair apart.

One thing is never normalised: a repeated query parameter. Writing ?tag=a&tag=b is entirely legal, and the standard does not say what it means, so every stack picked its own answer. PHP keeps the last value, Express collects them into an array, many frameworks and URLSearchParams.get take the first. The tool marks repeated keys rather than listing them twice without comment, because the resulting bug — a value that works in one service and vanishes in another — is genuinely hard to spot by reading.

Frequently asked questions

What is the difference between component and full-URL scope?
Component scope escapes the delimiters / ? : @ & = + as well, which is right for a single value placed inside a URL. Full-URL scope leaves them intact because there they separate the parts of the address. Using component scope on a whole URL turns every slash into %2F and produces one unusable string.
Why does my decoded text still contain a +?
Because decodeURIComponent follows RFC 3986, where + is just a plus. Form submissions and most query strings use form encoding, where + means a space. Turn on "treat + as space" when the text came from either.
How do I encode a plus that is really a plus?
Write it as %2B. A literal + in a query string will be read as a space by most parsers, so any plus that is genuinely part of your data — in a phone number, for instance — has to be escaped.
Should I encode the whole URL or just the parts?
Just the parts, and preferably not by hand: build the address with URL and URLSearchParams, which apply the correct encoding to each component. Encoding an assembled URL after the fact is where most double-encoding bugs come from.
Why is the breakdown showing a different URL from the one I pasted?
Because that is the one that gets sent. A URL is normalised before use: the scheme and host are lowercased, a default port is dropped, backslashes become slashes, an empty path becomes a slash, and a non-ASCII host becomes punycode. If you are comparing URLs or matching against an allow-list, compare these normalised forms rather than the raw text.
Why does no breakdown appear for example.com/path?
Because it is not an absolute URL — it has no scheme, so there is no host to identify. The tool will not guess one for you: example.com:8080 is already a valid absolute URL whose scheme is example.com and whose path is 8080, so silently prefixing https:// can produce a breakdown that looks reasonable and is wrong. Add the scheme yourself and the parts appear.
What is double encoding?
Encoding a value that was already encoded, so a %20 becomes %2520 — the percent sign itself gets escaped. It usually shows up as literal %20 sequences appearing in a page. Decode once and check whether you still see escapes; if you do, it was encoded twice.
Does encoding a value make it safe?
No. Percent-encoding is about transport, not safety. An encoded value is still whatever it was, and it needs the same validation and the same context-appropriate escaping before it reaches HTML, SQL or a shell.
Why is the fragment not sent to the server?
By design: everything after the # is handled by the browser alone and never appears in the request. That is why it cannot be seen in server logs, and why it was historically used for client-side routing.
Is what I paste sent anywhere?
No. Encoding, decoding and the URL breakdown all use the browser's own built-in functions and run entirely on your device. Nothing you paste ever leaves it.