What is URL encoding?
URL encoding — also called percent-encoding — is the standard mechanism for representing characters in a Uniform Resource Locator (URL) that would otherwise be illegal, ambiguous, or unsafe. The rules are codified in RFC 3986, the specification that defines URI syntax for the modern web.
The reason encoding exists is historical: URLs were designed in 1994 around a tiny alphabet of safe ASCII characters. Anything outside that set — spaces, accented letters, emojis, Chinese characters, query-string separators when used as data — needs to be expressed using a small, universally-portable code: a percent sign (%) followed by two hexadecimal digits representing one byte of the original character. The character A (ASCII 65) becomes %41; a space becomes %20; the character é (which takes two bytes in UTF-8) becomes %C3%A9.
Without encoding, URLs would silently break in unpredictable places. A search query like "hello world & friends" embedded raw into ?q=hello world & friends would be interpreted by the server as three separate parameters: q=hello world, friends, and an empty value. Encoded, it becomes ?q=hello%20world%20%26%20friends — unambiguous, parseable, and safe to transmit through every router, CDN, proxy, and log on the way to your server.
How URL encoding works under the hood
The percent-encoding algorithm has three steps and applies to every character that needs encoding:
- Step 1 — Convert to bytes. The character is converted to its byte representation in UTF-8 (the standard since RFC 3986 was updated in 2005). ASCII characters are 1 byte; accented Latin letters are 2 bytes; most CJK characters and emojis are 3–4 bytes.
- Step 2 — Express each byte as two hex digits. A byte has 256 possible values (0x00 to 0xFF), each representable in two uppercase hexadecimal digits.
- Step 3 — Prefix each hex pair with a percent sign. The output is a sequence of
%XXtriplets — one triplet per byte of the original character.
A worked example: encoding "café"
The string café contains four characters but five bytes in UTF-8 (because é is U+00E9, encoded as 0xC3 0xA9):
c→ 1 byte (0x63) → doesn't need encoding (unreserved ASCII letter)a→ 1 byte (0x61) → doesn't need encodingf→ 1 byte (0x66) → doesn't need encodingé→ 2 bytes (0xC3, 0xA9) → encoded as%C3%A9
Final encoded form: caf%C3%A9. The reverse — decoding — reads the %XX sequences back into bytes, then interprets the byte stream as UTF-8 to reconstruct the original characters.
%c3%a9) is technically valid, but uppercase is the canonical form. Many CDNs and caches normalize URLs to uppercase before generating cache keys — using lowercase risks cache misses on edge networks like Cloudflare and Akamai.
encodeURI vs encodeURIComponent — which one to use?
This is the single most-searched question in the URL encoding space — and getting it wrong is the most common bug in URL handling. JavaScript provides two functions with confusingly similar names but very different behavior. The right choice depends on what part of the URL you're encoding.
| Aspect | encodeURI() | encodeURIComponent() |
|---|---|---|
| Use case | Encoding a complete URL | Encoding a single value within a URL |
Preserves reserved chars (: / ? # & = +) |
Yes — leaves URL structure intact | No — encodes everything |
| Example input | https://x.com/search?q=hello world |
hello world & friends |
| Example output | https://x.com/search?q=hello%20world |
hello%20world%20%26%20friends |
| When to pick this | You have a full URL and want to make it safe to transmit (rare in practice) | You're building a URL piece by piece — encoding query values, path segments, or form data (most cases) |
| Common bug | Forgetting that it doesn't encode & or = — your data leaks into URL structure |
Encoding the whole URL with this — breaks ://, ?, & separators |
Rule of thumb: 95% of the time you want encodeURIComponent. It's the right tool for encoding individual values that you'll plug into a URL. encodeURI is for the rare case where you have a full, already-structured URL that just needs special-character escaping (e.g. spaces in path segments).
Reserved vs unreserved characters reference
RFC 3986 splits ASCII characters into three groups: unreserved (never encoded), reserved (have special meaning in URLs — encoded only when used as data), and everything else (always encoded, including spaces, control characters, and high-ASCII).
| Group | Characters | Encoded? |
|---|---|---|
| Unreserved | A–Z a–z 0–9 - _ . ~ |
Never |
| Reserved (gen-delims) | : / ? # [ ] @ |
Only when used as data, not as URL structure |
| Reserved (sub-delims) | ! $ & ' ( ) * + , ; = |
Only when used as data within a query value |
| Everything else | Space, " < > \ ^ { } | ` , all non-ASCII (UTF-8) |
Always |
Common encoded values cheat sheet
| Character | Encoded | When you'll see it |
|---|---|---|
| space | %20 or + | The most common — see "+ vs %20" section below |
! | %21 | Sometimes encoded, sometimes left raw — depends on the encoder |
" | %22 | Always encoded |
# | %23 | Encoded when in a query value (otherwise it's the fragment delimiter) |
& | %26 | Encoded inside query values to avoid splitting parameters |
+ | %2B | Critical — raw + means space in form-encoded URLs |
/ | %2F | Encoded only when used as data (not as path separator) |
= | %3D | Encoded inside query values to avoid splitting key=value |
? | %3F | Encoded inside query values (otherwise it's the query delimiter) |
% | %25 | Always encoded — to avoid being read as a percent-encoding prefix |
é | %C3%A9 | Two-byte UTF-8 character, two percent-triplets |
中 | %E4%B8%AD | Three-byte UTF-8 character (CJK) |
| 😀 (U+1F600) | %F0%9F%98%80 | Four-byte UTF-8 character (emoji) |
How to URL encode in 8 programming languages
Every modern language has standard-library URL encoding. Here are the canonical functions for the eight most-used languages, with one-line examples you can paste straight into your project.
JavaScript (browser + Node.js)
// Encode a query value
const value = "hello world & friends";
const encoded = encodeURIComponent(value);
// → "hello%20world%20%26%20friends"
// Decode
const decoded = decodeURIComponent(encoded);
// → "hello world & friends"
// Encode a full URL (rarely needed)
const fullUrl = encodeURI("https://x.com/search?q=hello world");
// → "https://x.com/search?q=hello%20world"
Python (3.x)
from urllib.parse import quote, quote_plus, unquote, unquote_plus
# Standard percent-encoding (spaces → %20)
quote("hello world & friends")
# → 'hello%20world%20%26%20friends'
# Form-style encoding (spaces → +)
quote_plus("hello world & friends")
# → 'hello+world+%26+friends'
# Decoding
unquote("hello%20world") # → 'hello world'
unquote_plus("hello+world") # → 'hello world'
PHP
// Form-style (spaces → +) — what HTML form submission uses
$encoded = urlencode("hello world & friends");
// → "hello+world+%26+friends"
// RFC 3986 percent-encoding (spaces → %20) — preferred for paths
$encoded = rawurlencode("hello world & friends");
// → "hello%20world%20%26%20friends"
// Decoding
$decoded = urldecode("hello+world"); // → "hello world"
$decoded = rawurldecode("hello%20world"); // → "hello world"
Java
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
// Note: Java's URLEncoder uses form-style encoding (spaces → +)
String encoded = URLEncoder.encode("hello world & friends", StandardCharsets.UTF_8);
// → "hello+world+%26+friends"
String decoded = URLDecoder.decode(encoded, StandardCharsets.UTF_8);
// → "hello world & friends"
Ruby
require 'cgi'
require 'uri'
# Form-style encoding (spaces → +)
CGI.escape("hello world & friends")
# → "hello+world+%26+friends"
# RFC 3986 percent-encoding (spaces → %20) — modern preferred
URI.encode_www_form_component("hello world & friends")
# → "hello+world+%26+friends"
# Decoding
CGI.unescape("hello+world") # → "hello world"
C# / .NET
using System;
using System.Web;
// HttpUtility — form-style encoding
string encoded = HttpUtility.UrlEncode("hello world & friends");
// → "hello+world+%26+friends"
// Uri.EscapeDataString — RFC 3986 (preferred for modern code)
string encoded2 = Uri.EscapeDataString("hello world & friends");
// → "hello%20world%20%26%20friends"
// Decoding
string decoded = Uri.UnescapeDataString("hello%20world");
Go
import "net/url"
// Form-style (spaces → +) — for query strings
encoded := url.QueryEscape("hello world & friends")
// → "hello+world+%26+friends"
// RFC 3986 path-style (spaces → %20)
encoded := url.PathEscape("hello world & friends")
// → "hello%20world%20&%20friends"
// Decoding
decoded, _ := url.QueryUnescape("hello+world")
Bash / cURL
# cURL has built-in URL encoding for data
curl -G --data-urlencode "q=hello world & friends" https://api.example.com/search
# Sends: GET /search?q=hello%20world%20%26%20friends
# Pure bash one-liner (POSIX-compatible)
encoded=$(printf '%s' "hello world" | jq -sRr @uri)
# → "hello%20world"
Note: JavaScript, Python's quote, PHP's rawurlencode, Ruby's URI.encode_www_form_component, C#'s Uri.EscapeDataString, and Go's PathEscape all produce the same RFC 3986 output. Java's URLEncoder, PHP's urlencode, and JavaScript's form mode use the older form-encoded variant where spaces become + instead of %20. Mixing these is the #1 source of "this works on my machine" bugs.
Common URL encoding scenarios
Query string parameters
The most common case. When building a URL like https://api.example.com/search?q=...&filter=..., encode each value with encodeURIComponent (or your language's RFC 3986 equivalent) before concatenating into the query string. Never encode the whole URL after building it — you'll either miss reserved characters or corrupt the structure.
Path segments
If your URL paths contain user-generated slugs that may include special characters (e.g. file names with spaces, names with accents), encode each path segment individually. /files/My Document.pdf becomes /files/My%20Document.pdf. Don't encode the slashes (/) themselves — those are path separators, not data.
Form submission (POST bodies)
Standard HTML forms send data as application/x-www-form-urlencoded: keys and values are URL-encoded, spaces become + (not %20), and pairs are joined by &. Most language libraries (e.g. FormData in JS, requests.post(data=...) in Python) handle this automatically — but if you build form bodies by hand, use the form-encoding variant of your language's URL encoder.
Cookie values
Cookies cannot contain raw spaces, semicolons, or commas. Browsers do not encode cookie values automatically — your application must encode before document.cookie = and decode after reading. Most cookie libraries handle this for you.
HTTP headers and OAuth signatures
Some HTTP headers (notably OAuth 1.0 Authorization headers and certain custom headers) require RFC 3986 percent-encoding. OAuth specifically forbids the +-for-space variant — using the wrong encoder breaks signature verification with cryptic 401 errors. When testing API requests with encoded query strings, the HTTP request builder shows the wire format your server will actually receive.
URL encoding for SEO-friendly slugs
The cleanest URL is one that needs no encoding at all. Lower-case ASCII letters, digits, and hyphens (the unreserved set) pass through every parser unchanged and look identical in browser address bars, social-card previews, and email clients. Building URLs from user input? Pass titles through the URL slug generator first to strip diacritics, transliterate Cyrillic/Greek/CJK to ASCII, and produce hyphenated lower-case output that bypasses encoding entirely.
+ vs %20 — the eternal confusion explained
Both + and %20 represent a space character — but they're not interchangeable, and using the wrong one in the wrong place causes bugs that are hard to track down. Here's the rule:
| Context | Use | Why |
|---|---|---|
URL path (e.g. /files/my%20doc.pdf) |
%20 |
RFC 3986 standard. Raw + in a path is treated as a literal plus sign, not a space. |
URL query string (e.g. ?q=hello%20world) |
%20 preferred, + tolerated |
Modern web apps accept both. %20 is unambiguous; + is the legacy form-encoded convention. |
HTML form submission body (POST application/x-www-form-urlencoded) |
+ |
Defined by the HTML spec. The form encoder produces + for spaces, percent-encoding for everything else. |
| OAuth 1.0 signatures | %20 only |
OAuth 1.0 spec mandates RFC 3986. Using + breaks the signature. |
The trap: if you encode "hello world" using a form-style encoder, you get hello+world. If a downstream parser expects RFC 3986 (where + is a literal plus), it decodes your value as hello+world instead of hello world. Spaces become plus signs in your database. This actually happens in production constantly — usually when the front-end uses one library and the back-end uses another.
Double encoding — what it is and how to avoid it
Double encoding happens when an already-encoded string is encoded again. The percent sign itself (%) gets encoded as %25, so %20 turns into %2520. The result still looks like a URL but contains literal %20 sequences instead of actual spaces.
The classic scenario: a URL is built with proper encoding by component A, then component B (which doesn't realize it's already encoded) encodes it again before passing it on. The downstream parser decodes once, gets back the half-encoded version, treats %20 as literal text, and serves the wrong page (or a 404).
How to detect double encoding
- Look for
%25sequences in URLs that should not contain literal percent signs. %2520is the giveaway — that's a double-encoded space.- If your URL has
%26amp%3Binstead of%26, an HTML escape was applied on top of URL encoding.
How to fix it
- Decode once, then re-encode — strip the doubled layer with a single decode pass, then encode the raw value once.
- Audit your encoding boundary. Encode at exactly one point — typically the moment you concatenate values into a URL. Document this in the code.
- Don't encode in templates AND in code. If your template engine auto-escapes URL parameters (as some MVC frameworks do), don't pre-encode the values you pass in.
%XX sequences, you have one more encoding layer to peel off.
URL encoding bugs in popular frameworks (2026)
Most production URL-encoding bugs aren't in your code — they're in the framework's defaults differing from yours. The cheat sheet:
| Framework / Library | Encoding default | Common gotcha |
|---|---|---|
| Next.js (router) | RFC 3986 | Dynamic routes [slug] auto-decode %2F to / — strict-routing config needed if your slug must contain a literal slash. |
| Express.js | req.query uses qs (form-decoded) | + in query is decoded as space. Use req.url + URL constructor for RFC 3986. |
| Flask / Django | Werkzeug / Django uses form-encoding | request.GET turns + into space silently. Use urllib.parse.unquote with plus=False for path segments. |
| Java Spring | RFC 3986 in UriComponentsBuilder | HttpServletRequest.getParameter form-decodes; mixing the two on the same request leaks raw + to logs. |
cURL --data-urlencode | Form-encoding | Encodes + as %2B by default — opposite of what most JS encoders do. Verify with --trace-ascii. |
| fetch() / Axios | browser uses RFC 3986 for URL strings | If you build the body as a string yourself, no encoding happens — pass URLSearchParams or FormData instead. |
To inspect what your client actually puts on the wire, paste the URL into the decoder above, then build the same request through the HTTP request builder — you'll see the raw query string before any framework-side reinterpretation.
encodeURIComponent vs URLSearchParams in JavaScript
Two ways to build a query string in 2026, and only one of them handles + correctly without manual escaping. encodeURIComponent('a b') returns 'a%20b' (space → %20), while new URLSearchParams({q: 'a b'}).toString() returns 'q=a+b' (space → +). Both are valid for the query string, but the receiving server has to know which to expect — most APIs accept either. Use URLSearchParams for full query objects (it handles arrays, repeated keys, and special chars correctly) and use encodeURIComponent for individual segments you concatenate by hand. Never use encodeURI on a value — it leaves reserved characters like ?, &, = untouched, which corrupts the surrounding URL.
Decoding a URL with international characters (Cyrillic, CJK, emoji)
Non-ASCII characters in URLs become long percent-encoded sequences. 'москва' encodes to %D0%BC%D0%BE%D1%81%D0%BA%D0%B2%D0%B0 (each Cyrillic letter is 2 UTF-8 bytes → 6 hex chars). '東京' encodes to %E6%9D%B1%E4%BA%AC (each CJK char is 3 bytes → 9 hex chars). Emoji are 4 bytes (%F0%9F%8C%8D = 🌍). The decoder above renders all of these back to the original glyphs; decodeURIComponent handles the UTF-8 reassembly automatically. For URL paths that need to look readable, transliterate non-Latin scripts to ASCII first using the slug generator rather than letting the encoder turn every char into a 9-byte hex string.
Best URL encoder online for 2026 — what to look for
"URL encoder" is one of the most generic queries in dev tooling — there are hundreds of them, and they don't all behave the same way. The differences that actually matter when you pick one:
| Tool | Mode | UTF-8 / Unicode | Both encode & decode | Logs your input |
|---|---|---|---|---|
| This tool (FreeDevTool) | Browser-only | Yes — handles Cyrillic/CJK/emoji | Yes, with auto-detect | No — no requests on encode/decode |
| urlencoder.org | Server-side | Yes | Yes | Server-side processing — input transits to their host |
| urldecoder.org | Server-side | Yes | Decode only on landing page | Server-side processing |
| Postman / Insomnia | Desktop app | Yes | Auto-encodes query params | Account-bound — workspaces sync to cloud |
cURL --data-urlencode | CLI | Yes | Encode only | Local |
Browser DevTools console (encodeURIComponent) | Browser-only | Yes | Manual decodeURIComponent | Local |
The single test that separates good encoders from broken ones: paste café and verify the output is caf%C3%A9 (UTF-8 bytes, RFC 3986) — not caf%E9 (Latin-1, the 1990s default that breaks every modern API). If the encoder above your input does not produce %C3%A9 for that test, do not use it for anything that touches a 2026 backend.
How do I URL encode a string online without uploading it?
Two signals confirm a URL encoder is fully client-side: (1) the page works with your network disconnected — try it now: turn off wifi, paste a string, click encode, see the result; (2) opening DevTools → Network shows zero requests when you encode or decode. The encoder above passes both. The encoding logic is a few lines of JavaScript wrapping encodeURIComponent, encodeURI, and decodeURIComponent — view-source any time. For sensitive payloads (auth tokens in query params, internal API URLs), prefer a tool like this one over server-side encoders that accept your input as a POST body.
What's the difference between URL encode, decode, escape, and unescape?
Four near-synonyms that mean different things in different ecosystems:
- URL encode (= percent-encode = RFC 3986) — the standard. Spaces become
%20, non-ASCII becomes UTF-8 percent-triplets. JavaScript'sencodeURIComponent, Python'surllib.parse.quote. - URL decode — the inverse of encode. Reads
%XXsequences as bytes, reassembles UTF-8 codepoints. JavaScript'sdecodeURIComponent, Python'surllib.parse.unquote. escape()/unescape()— JavaScript built-ins, deprecated since 1999. They use Latin-1 for codepoints < 256 and a non-standard%uXXXXfor higher codepoints. Don't use them. They corrupt UTF-8 and produce output no other language can decode.- HTML entity encode — a different problem.
&→&. Applies inside HTML markup, not URLs. Pages mixing the two layers up produce%26amp%3B-style nightmares (URL-encoded HTML entity).
The encoder above does the first two. For the fourth, use the HTML entity encoder — different tool, different layer, never mix them.
Percent encoding in JavaScript — encodeURIComponent and decodeURIComponent
Percent encoding in JavaScript is handled by three native functions: encodeURIComponent() for individual values, encodeURI() for full URLs, and decodeURIComponent() for the inverse. The encoder above wraps these directly — paste a value, get the percent-encoded string instantly. encodeURIComponent('hello world&test') returns 'hello%20world%26test'; only the unreserved set (A-Z a-z 0-9 - _ . ~) passes through untouched. To percent-encode a URL the way most APIs expect — full RFC 3986 with UTF-8 byte sequences — always reach for encodeURIComponent, never the deprecated escape().
HTML percent codes vs URL percent codes — they're not the same thing
"HTML percent codes" usually means HTML numeric character references (% → %, & → &). "URL percent codes" are RFC 3986 percent-encoding (%25 → %, %26 → &). The numbers look similar but the syntax and the layer they apply to are different — HTML entities apply inside HTML markup, URL percent codes apply inside URLs. Mixing them produces values like %26amp%3B (URL-encoded HTML entity for ampersand) — a sign you've encoded the same character twice through two different schemes. The encoder above handles only the URL layer; for the HTML side use the HTML entity encoder.
urlencode in PHP, Python, and JavaScript — the differences that bite
urlencode is one name for three different operations across languages. In PHP, urlencode($str) form-encodes (spaces → +); rawurlencode($str) does RFC 3986 (spaces → %20). In Python, urllib.parse.quote(str) is RFC 3986; urllib.parse.quote_plus(str) is form-encoded. In JavaScript, encodeURIComponent is RFC 3986; there's no built-in form-encoder — use URLSearchParams for form-style. The encoder above uses RFC 3986 by default with a toggle for form-style — paste a string and switch modes to see the diff.
Encode URL online with Unicode — the test that catches broken encoders
Many older "encode URL online" tools still emit Latin-1 or non-standard %uXXXX sequences. Test any encoder with the string café — the correct RFC 3986 output is caf%C3%A9 (UTF-8 bytes). If the tool returns caf%E9 (Latin-1, the 1990s default) or caf%u00E9 (deprecated escape() output), it will silently corrupt every emoji, every CJK character, every Cyrillic name in your data. The encoder above is verified UTF-8 — paste any Unicode and check the round-trip.
URL encoder alternative to urlencoder.org and meyerweb.com
The two long-running web URL encoders are urlencoder.org (server-side) and meyerweb.com/eric/tools/dencoder/ (a 2002 Eric Meyer page using a JavaScript prompt-and-replace). Both still work; reasons to prefer this tool in 2026:
- Modern UTF-8 by default. Eric Meyer's tool predates broad UTF-8 adoption and uses
escape()in places — it produces%uXXXXfor non-ASCII, which no current API decodes. - Both directions in one page. urlencoder.org splits encode and decode into separate pages with separate ads. This page auto-detects which direction your input needs.
- No ads, no tracking pixels. Both alternatives load multiple ad-network and analytics scripts. This page loads only Google Analytics 4 (you can block it) and the Web Crypto API.
- Side-by-side breakdown when decoding. Decoding a URL with mixed query parameters reveals each component as a row, not a single decoded blob — easier to find which parameter has the bug.
For workflows that need encoding + structural URL inspection, also try the HTTP request builder (which encodes query params automatically and shows the resulting wire format) and the slug generator (when the input is a page title and the output should be a clean URL with no encoding at all).
URL encoding and SEO — best practices for 2026
Google can index URLs with percent-encoded characters, but heavily encoded URLs hurt click-through rate and shareability. The cleanest URLs are the ones that don't need encoding at all.
- Use hyphens, not spaces, in slugs.
/my-blog-postis far better than/my%20blog%20post— easier to read, no truncation in social previews, no fragility around encoding/decoding. - Lowercase everything. URLs are technically case-sensitive in the path.
/Aboutand/aboutcan be different pages — Google treats them as duplicates, but you fragment your link equity. Pick lowercase site-wide. - Avoid non-ASCII in URL paths.
/résuméworks but appears as/r%C3%A9sum%C3%A9in browser address bars and shared links. For international content, use ASCII transliteration (/resume) or punycode for domains. - Keep encoding to query parameters. Path segments should be predictable, indexable, and short. Save the messy encoding for query strings, where users don't read the URL.
- Use our URL Slug Generator to convert page titles into clean, encoding-free slugs automatically.