What Is URL Encoding?

URL encoding, also called percent-encoding, converts characters that are not safe for use in a URL into a format that is. It works by replacing the unsafe character with a percent sign followed by two hexadecimal digits representing that character's byte value. A space becomes %20. An @ symbol becomes %40. A forward slash used as literal data rather than a path separator becomes %2F.
URLs are restricted to a small alphabet by design: letters A through Z, digits 0 through 9, and a handful of punctuation marks such as -, _, ., and ~. Anything outside that alphabet either has a reserved structural meaning or is not permitted at all, and percent-encoding is the mechanism that lets you carry arbitrary data through that narrow channel safely.
Worked Examples, Character by Character
The clearest way to understand encoding is to walk through specific characters and see exactly what happens to them.
| Character | Encoded | Why it needs encoding |
|---|---|---|
| Space | %20 (or + in form data) |
Spaces terminate URLs in many contexts and break parsing |
& |
%26 |
Separates query string key-value pairs, so a literal ampersand in a value would split it into two parameters |
= |
%3D |
Separates a key from its value; a literal equals sign in a value confuses the parser |
+ |
%2B |
In form encoding, a bare plus sign already means space, so a literal plus must itself be escaped |
/ |
%2F |
Normally a path separator; must be encoded when it is part of a value, such as inside a base64url token |
@ |
%40 |
Used for userinfo in URL authority syntax; encoding it lets you place an email address inside a query value safely |
# |
%23 |
Marks the start of a fragment identifier; a literal hash in a value would truncate the rest of the URL in a browser |
? |
%3F |
Marks the start of the query string; a literal question mark in a value can confuse naive parsers |
: |
%3A |
Used in scheme and port syntax; needs encoding when it appears as literal data, such as inside an otpauth label |
| e with acute accent (UTF-8 bytes 0xC3 0xA9) | %C3%A9 |
Non-ASCII characters must first be converted to UTF-8 bytes, then each byte is percent-encoded |
| Emoji, for example a single smiling face (4-byte UTF-8) | %F0%9F%98%80 |
Multi-byte UTF-8 sequences produce multiple percent triplets, one per byte, not one per visible character |
Full otpauth URI Encoding Walkthrough
Authenticator apps use a URI scheme called otpauth to transfer a 2FA secret along with its label and issuer. Because the label typically includes an account name and often an email address, it is a perfect real-world case for percent-encoding in practice.
Start with the raw, unencoded pieces:
Scheme: otpauth
Type: totp
Issuer: My Service
Account: alice@example.com
Secret: JBSWY3DPEHPK3PXP
The label portion of the URI is normally built as Issuer:Account, so before encoding it reads My Service:alice@example.com. That string contains a space, a colon, and an at sign, all of which need to be encoded before they can safely sit in the path portion of the URI:
Raw label: My Service:alice@example.com
Space encoded: My%20Service%3Aalice%40example.com
Full URI:
otpauth://totp/My%20Service%3Aalice%40example.com?secret=JBSWY3DPEHPK3PXP&issuer=My%20Service
Notice that the issuer also appears a second time as a query parameter, and it needs to be encoded there too, separately from the label. This is a common source of bugs when generating QR codes or setup links by hand: developers often encode the label but forget the issuer parameter, or encode the whole string once and then the individual parameter again, which leads to the double-encoding problem covered later in this guide.
URL Encoding vs Form Encoding: A Concrete Bug
Two related but different encoding schemes get confused constantly, and the confusion is not just academic, it causes real production bugs.
- Percent-encoding (RFC 3986) is the general standard for URLs. A space becomes
%20. application/x-www-form-urlencodedis what browsers use when submitting HTML forms. A space becomes+, and a literal plus sign must itself be escaped as%2B.
Here is a bug that this distinction causes in practice. Imagine an API endpoint that accepts a search term as a query string parameter, and the term itself might contain a plus sign, for example a user searching for "C++ developer". A frontend built with a plain string concatenation might produce:
Naive URL: https://example.com/search?q=C++%20developer
On the server side, if that query string is parsed using form-decoding rules rather than strict percent-decoding, both literal plus signs are silently converted to spaces, because form decoding treats + as meaning space. The result the server actually sees is C developer, with the plus signs gone entirely, not C++ developer. The fix is to properly encode the plus signs as %2B before building the URL, giving q=C%2B%2B%20developer, which decodes correctly under both rule sets. This exact bug shows up repeatedly in search boxes, tag filters, and version-string parameters that contain a literal plus character.
Encoding in Different Languages, With the Gotchas
| Language | Function | Gotcha to watch for |
|---|---|---|
| JavaScript | encodeURIComponent(str) |
Encodes almost everything, including /, ?, and &. Do not use it on a full URL or it will break the scheme and path structure, use it only on individual parameter values. |
| JavaScript | encodeURI(str) |
Preserves structural characters like / and ?, but this means it will not protect a value that itself contains those characters, so it is unsafe for encoding individual parameters. |
| Python | urllib.parse.quote(str) |
By default leaves / unencoded (useful for paths, dangerous for values). Pass safe='' to encode everything, and use quote_plus() specifically when producing form-encoded data. |
| PHP | urlencode($str) |
This is form encoding, spaces become +. Developers frequently reach for this by habit and then wonder why a plus sign in their data turns into a space on decode. Use rawurlencode() for RFC 3986 percent-encoding instead. |
| Go | url.QueryEscape(str) |
Also form encoding, matching PHP's urlencode behavior. Use url.PathEscape() when the value is a path segment rather than a query parameter, since the escaping rules differ slightly between the two. |
| Java | URLEncoder.encode(str, "UTF-8") |
Also encodes spaces as +, a frequent surprise for developers expecting %20. There is no built-in strict RFC 3986 equivalent in the standard library, so many teams write a small wrapper that replaces + with %20 after calling it. |
Double-Encoding: How It Happens and How to Spot It
Double-encoding occurs when a string that is already percent-encoded gets encoded a second time. The percent sign itself is not in the unreserved character set, so on a second pass it gets converted to %25. A space that should read %20 ends up as %2520 instead.
This typically happens in multi-layer systems, a frontend encodes a value, passes it to a backend service that encodes the whole request again before forwarding it, or a redirect handler re-encodes a URL it received that was already encoded upstream. The symptom is usually a parameter that looks fine in your browser address bar but decodes to garbage or fails validation on the server. If you see repeated %25 sequences in logs or captured requests, that is the signature of double-encoding, and the fix is to find the extra encoding step and remove it, not to add a second decoding step to compensate.
Encode and Decode URLs Instantly
Use our free URL Encoder/Decoder to percent-encode or decode any string instantly in your browser. Nothing is sent to a server, the conversion happens entirely on your device. It is a useful companion when you are debugging exactly this kind of encoding mismatch, since you can paste a raw string in and compare it against what your application is producing.
Frequently Asked Questions
What is the actual difference between encodeURI and encodeURIComponent in JavaScript?
encodeURI() is meant for a complete URL, so it deliberately leaves structural characters like /, ?, &, and # alone. encodeURIComponent() encodes everything outside the unreserved character set, which makes it the correct choice for encoding a single query string value or path segment before inserting it into a larger URL.
Should I encode an entire URL, or just the parameter values inside it?
Only the parameter values. Encoding an entire URL, including the scheme and slashes, would break its structure and make it unusable. When you are building a URL from pieces programmatically, run each value through encodeURIComponent() or your language's equivalent, then assemble the final string with the literal & and = separators around the already-encoded values.
Why does my otpauth QR code fail to scan after I built the URI by hand?
The most common cause is an unencoded space or colon in the label portion of the URI, or an issuer parameter that was left unencoded while the label was encoded. Walk through the worked example above and confirm every reserved character in both the label and the issuer parameter has been percent-encoded before the URI is turned into a QR code.
Why did a plus sign in my search query turn into a space?
Your value was decoded using form-decoding rules, where a bare + always means space. If your data can legitimately contain a literal plus character, encode it as %2B before it goes into the URL, as shown in the C++ search example above.
Is percent-encoding the same thing as base64 encoding?
No, they solve different problems. Percent-encoding makes arbitrary text safe to place inside a URL. Base64 converts binary data into printable ASCII text for transport in contexts like email attachments or Basic Auth headers. See our guide on what Base64 encoding is for the difference in detail, and our HTTP Basic Authentication guide for a real example of Base64 in use.