codr.tools
Tools

URL encoder / decoder

Percent-encode and decode URLs and query strings, with encodeURIComponent, encodeURI, strict RFC 3986 and form encoding, plus a breakdown of the query parameters.

  1. URL encoder / decoder
  2. Base64 encoder / decoder
Direction
Direction
In50 bytes
Out86 bytes
Swap
Ratio×1.72
StatusValid

URL breakdown

Protocol
https
Host
example.com
Path
/menu
Query parameters, decoded
ParameterValue
qthé glacé 🧊
page2

Escapes everything except A-Z, a-z, 0-9 and - _ . ! ~ * ' ( ). Use it for a single query value or path segment.

Percent-encode and decode URLs and query strings, with encodeURIComponent, encodeURI, strict RFC 3986 and form encoding, plus a breakdown of the query parameters.

  1. URL encoder / decoder
  2. Base64 encoder / decoder

How to use this tool#

  1. Pick a direction. Encode turns text into percent-escapes. Decode turns them back. The conversion runs as you type, so there is no button to press.
  2. Choose the encoding rules. Component is the right answer most of the time: it escapes one value that is going into a URL. Full URL keeps the characters that hold a URL together. The other two are covered below.
  3. Paste your text over the example and read the result underneath. The counts under each box give characters and UTF-8 bytes, which is what a length limit usually means.
  4. Use Swap to check your work. It moves the output back into the input and flips the direction, so you can see that a value survives the round trip unchanged.
  5. Watch the breakdown. When either side is a full address, a table appears under the output with the protocol, host, path and every query parameter, decoded.

When decoding, Read + as a space is on by default, because most text people paste in comes from a query string. Turn it off when you are decoding a path segment, where a plus sign means a plus sign.

What percent-encoding is#

A URL is a line of ASCII text with structure: ? starts the query, &separates parameters, / separates path segments. So any data you put inside a URL has to avoid those characters, and anything that is not plain ASCII has to be written in ASCII somehow. Percent-encoding is how. RFC 3986 section 2.1 defines it as "a character triplet, consisting of the percent character "%" followed by the two hexadecimal digits representing that octet's numeric value". A space is octet 0x20, so it becomes %20.

Anything outside ASCII is first turned into bytes, then those bytes are escaped one by one. Section 2.5 says the data "should first be encoded as octets according to the UTF-8 character encoding", which is why é comes out as %C3%A9: two bytes, two escapes. An emoji takes four bytes and four escapes.

url
https://example.com/photos/été?q=a b&tag=c#top

  été   non-ASCII   ->  %C3%A9t%C3%A9   UTF-8 bytes first, one escape per byte
  " "   space       ->  %20             or "+", in form encoding only
  "&"   separator   ->  %26             but only when it is data, not a separator
  "#"   fragment        everything after it never reaches the server

Case does not matter when decoding: RFC 3986 says %2F and %2f are equivalent, though producers "should use uppercase hexadecimal digits". This tool emits uppercase.

Reserved and unreserved characters#

RFC 3986 sorts the ASCII characters into groups, and which group a character is in decides whether it needs escaping.

  • Unreserved (section 2.3): letters, digits, and - ._ ~. These never need escaping, and escaping them changes nothing. If you see %7E where a ~ would do, an older library wrote it.
  • Reserved (section 2.2), in two sets. The gen-delims :/ ? # [ ] @ split a URL into its major parts. The sub-delims ! $ &' ( ) * + ,; = are delimiters inside those parts.
  • Everything else, including space, < >" { } | \^ ` and every non-ASCII character, must be escaped.

The rule for reserved characters is about intent. RFC 3986 section 2.2: "If a reserved character is found in a URI component and no delimiting role is known for that character, then it must be interpreted as representing the data octet corresponding to that character's encoding in US-ASCII." In other words, an & that is meant as data and not as a separator has to be written %26, or the far end will split on it.

encodeURIComponent or encodeURI?#

JavaScript ships two encoders, and picking the wrong one is the single most common URL bug. They differ only in which characters they leave alone.

  • encodeURIComponent() leaves onlyA-Z a-z 0-9 - _ . ! ~ * ' ( ) and escapes everything else, including/, ?, & and =. Use it on one pieceof data you are about to drop into a URL.
  • encodeURI() leaves all of the above plus; / ? : @ & = + $ , #. Use it on a whole URL that is already correctly structured and just has some unsafe characters in it, like a space in the path.

That is the whole difference. On this page they are the Component andFull URL modes.

Strict RFC 3986 mode#

encodeURIComponent() predates RFC 3986 and leaves five characters unescaped that the RFC lists as reserved sub-delimiters: ! ' ( )*. Most servers do not care. Some do: OAuth 1.0 signatures, AWS request signing and anything that re-encodes and compares strings will disagree with you over an unescaped apostrophe. The Component, strict RFC 3986 mode escapes those five as well. It is the same thing MDN's snippet does:

javascript
// encodeURIComponent leaves ! ' ( ) * alone although RFC 3986
// reserves them. This is MDN's strict version.
function encodeRFC3986URIComponent(str) {
  return encodeURIComponent(str).replace(
    /[!'()*]/g,
    (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,
  );
}

Why + sometimes means space#

There is no +-means-space rule in RFC 3986. A plus sign is a sub-delimiter, and in a path or a generic URI component it stands for itself. The convention comes from HTML form submission, and it applies to exactly one thing: theapplication/x-www-form-urlencoded format, which is the body of a form POST and the query string a form GET produces.

The WHATWG URL Standard defines the parser for that format, and one of its steps is simply: "Replace any 0x2B (+) in name and value with 0x20 (SP)." The serializer does the reverse. Everything else in a URL leaves+ alone.

  • Use + when you are producing or reading form data: a<form> submission, a URLSearchParams string, Python'squote_plus, Go's url.QueryEscape, PHP's urlencode.
  • Use %20 everywhere else: path segments, fragments, headers, anything read by a plain RFC 3986 parser. %20 is also correct inside form data, since decoders unescape it too. When in doubt, %20 is the safer of the two.

A detail worth knowing: decodeURIComponent() does not convert+ to a space. If you decode a query string with it and gethello+world, that is why. The Read + as a space checkbox on this page does the extra step for you.

The form mode here matches URLSearchParams exactly, down to the detail that it escapes ! ' ( ) ~ but leaves* alone.

Two mistakes that cause most of the bugs#

Encoding twice

Encode a b and you get a%20b. Encode that and you geta%2520b, because the % itself becomes %25. One decode leaves you with the literal text a%20b, which is not what anyone wanted. RFC 3986 section 2.4 states the rule directly: "Implementations must not percent-encode or decode the same string more than once."

It usually happens when a value is encoded by a helper and then encoded again by the framework that builds the URL. If you see %25 in a URL that should not contain a percent sign, that is the symptom. Decode twice here to confirm, then remove one of the two encoding steps.

Encoding a whole URL as a component

Running a complete address through encodeURIComponent() turnshttps://example.com/a into https%3A%2F%2Fexample.com%2Fa. That is correct and useful when the URL is a value, such as a ?redirect= target. It is wrong when you just wanted to tidy up an address, because nothing will fetch it any more. Use Full URL mode for that.

Encoding a URL in code#

JavaScript

javascript
// One value going into a query string.
const q = encodeURIComponent('a b&c=d');   // 'a%20b%26c%3Dd'
const url = `https://example.com/s?q=${q}`;

// A whole URL that is already well formed, with unsafe characters in it.
encodeURI('https://example.com/a b?q=1');  // 'https://example.com/a%20b?q=1'

// Building a query string: URLSearchParams escapes and joins for you,
// writing spaces as "+" because that is form encoding.
new URLSearchParams({ q: 'a b', tag: 'c' }).toString();  // 'q=a+b&tag=c'

// Decoding. decodeURIComponent does not turn "+" into a space.
decodeURIComponent('a%20b');   // 'a b'
decodeURIComponent('a+b');     // 'a+b'
decodeURIComponent('a+b'.replace(/\+/g, ' '));  // 'a b'

Python

python
from urllib.parse import quote, quote_plus, urlencode

quote('a b/c')              # 'a%20b/c'   safe='/' by default
quote('a b/c', safe='')     # 'a%20b%2Fc'
quote_plus('a b/c')         # 'a+b%2Fc'   spaces become "+"

urlencode({'q': 'a b'})                    # 'q=a+b'
urlencode({'q': 'a b'}, quote_via=quote)   # 'q=a%20b'

Go

go
import "net/url"

url.QueryEscape("a b/c")  // "a+b%2Fc"   spaces become "+"
url.PathEscape("a b/c")   // "a%20b%2Fc"

url.QueryUnescape("a+b")  // "a b", nil
url.PathUnescape("a+b")   // "a+b", nil   "+" stays a plus sign

PHP

php
<?php
rawurlencode('a b/c');  // 'a%20b%2Fc'   RFC 3986
urlencode('a b/c');     // 'a+b%2Fc'     form encoding, spaces become "+"

rawurldecode('a+b');    // 'a+b'
urldecode('a+b');       // 'a b'

curl

shell
# curl encodes the value for you and sends it as a POST body.
curl --data-urlencode "q=hello world & more" https://example.com/search

# Only the part after "=" is encoded; the name is assumed to be safe already.
# Use "@file" to encode the contents of a file instead.
curl --data-urlencode "note@message.txt" https://example.com/notes

Nothing here leaves your browser#

The encoding and decoding happen on your machine, in JavaScript, after the page has loaded. The text you paste is never sent anywhere, and there is nowhere to send it: this site is static files with no backend. Disconnect from the network and the tool keeps working, which is the quickest way to prove it. That matters if the URL you are debugging has a session token or an API key in it.

Questions people ask#

Why did my & break the query string?

Because it was read as a separator. A value containing & has to be escaped to%26 before it is joined into a query string, otherwise the server sees two parameters where you meant one. Same for = inside a value, and #, which starts the fragment and makes everything after it invisible to the server. Component mode handles all three.

Is there a length limit on a URL?

Not in the standard, but there is in practice, and percent-encoding makes strings longer: every escaped byte becomes three characters, so an escaped emoji is twelve characters. Browsers and servers set their own ceilings, commonly around 2,000 to 8,000 characters. If a value is long enough for this to be a question, send it in a request body instead.

What does %% or a lone % mean?

Nothing valid. A % must be followed by two hexadecimal digits, so a literal percent sign in data has to be written %25. If you decode text with a stray %here, the tool points at the character rather than failing, and shows you everything it could read.

Why does my decoded text show squares?

Because the escapes decode to bytes that are not valid UTF-8. That usually means the URL was built by something that used Latin-1, so é was written as %E9 rather than %C3%A9. There is no way to be sure what encoding was intended, so this tool marks the undecodable bytes and tells you where the first one is rather than guessing.

Should I escape a space as %20 or +?

%20, unless you are specifically producing form data. Both are read correctly by form decoders, but only %20 is correct in a path, a fragment or a plain URI component.

Does uppercase or lowercase hex matter?

Not for correctness. %2F and %2f decode identically and RFC 3986 declares the two forms equivalent. It matters if something signs or compares the encoded string byte for byte, which is why the RFC recommends uppercase and why this tool emits it.

  • Encode text or a file to Base64 and decode it back, with correct UTF-8, standard and URL-safe alphabets, optional padding, MIME line wrapping and data: URIs.

  • Convert identifiers and text to camelCase, PascalCase, snake_case, SCREAMING_SNAKE_CASE, kebab-case, Train-Case, dot.case, path/case and four more at once, one line at a time, with acronyms and digits split the way style guides expect.

  • Generate random v4 and time-ordered v7 UUIDs in your browser, one at a time or up to 1000 at once, and inspect any UUID you paste in.

  • Every status code in the IANA registry, with its official reason phrase, a plain explanation, the defining RFC and a link straight to the code, plus the vendor codes from nginx and Cloudflare that are not in the registry.