URL Encoding Explained: A Developer's Complete Guide
This guide has a free tool → Open URL Encoder/Decoder
URL Encoding Explained: A Developer's Complete Guide
URL encoding is one of those foundational web concepts that most developers encounter daily but rarely stop to fully understand. Misusing it leads to broken URLs, security vulnerabilities, and subtle bugs that are frustrating to diagnose. This guide covers everything: the specification, the rules, language-specific implementations, common mistakes, and how to handle real-world scenarios.
---
What Is URL Encoding?
URL encoding, formally called percent-encoding, is the process of replacing unsafe or reserved characters in a URL with a percent sign followed by two hexadecimal digits representing the character's byte value in UTF-8.
The name "percent-encoding" comes from the % character used as the escape prefix. For example:
- A space character (ASCII 32, hex 20) becomes
%20 - An ampersand (ASCII 38, hex 26) becomes
%26 - A forward slash (ASCII 47, hex 2F) becomes
%2F
Original: https://example.com/search?q=hello world&lang=en
Encoded: https://example.com/search?q=hello%20world&lang=enURLs can only safely contain a limited set of characters as defined by RFC 3986 (the current URL standard, published in 2005). Any character outside that set must be percent-encoded before appearing in a URL. This applies to query string values, path segments, usernames, passwords, and fragment identifiers.
---
Why URL Encoding Exists
The internet was designed around the ASCII character set, and URLs were specified to use only a subset of ASCII characters that are unambiguous across different systems, protocols, and network configurations. As the web became global, the need to represent non-ASCII characters (like é, ü, 中, or 🔒) in URLs became essential.
Percent-encoding provides a mechanism to encode any byte sequence using only the safe ASCII characters %, 0-9, and A-F. This makes URLs safe to:
- Transmit over protocols that strip or alter non-ASCII bytes
- Display in contexts that only support ASCII
- Parse unambiguously regardless of locale or character encoding settings
- Include in email, log files, and other text contexts without corruption
---
The URL Character Classification
RFC 3986 divides all characters into three categories:
Unreserved Characters (Never Encoded)
These 66 characters are safe to appear anywhere in a URL without encoding:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z
0 1 2 3 4 5 6 7 8 9
- _ . ~If you are building a URL and your value consists only of these characters, no encoding is needed.
Reserved Characters (Context-Dependent)
These characters have special structural meaning in URLs. They should only be encoded when used outside their intended structural role:
| Character | URL Role | Encoded Form |
|---|---|---|
: | Separates scheme from authority (https:) | %3A |
/ | Separates path segments | %2F |
? | Begins the query string | %3F |
# | Begins the fragment identifier | %23 |
[ | Used in IPv6 address literals | %5B |
] | Used in IPv6 address literals | %5D |
@ | Separates user info from host | %40 |
! | Sub-delimiter | %21 |
$ | Sub-delimiter | %24 |
& | Separates query parameters | %26 |
' | Sub-delimiter | %27 |
( | Sub-delimiter | %28 |
) | Sub-delimiter | %29 |
* | Sub-delimiter | %2A |
+ | Space in form-encoded query strings | %2B |
, | Sub-delimiter | %2C |
; | Sub-delimiter | %3B |
= | Separates query parameter name from value | %3D |
The key rule: if one of these characters appears as data (not as a structural separator), it must be encoded. For example, if a query parameter value contains an &, it must be encoded as %26 to prevent the parser from treating it as a parameter separator.
Characters That Must Always Be Encoded
Everything else must be encoded. This includes:
| Character | Description | Encoded Form | |
|---|---|---|---|
| Space | %20 or + | |
" | Double quote | %22 | |
< | Less than | %3C | |
> | Greater than | %3E | |
{ | Left curly brace | %7B | |
} | Right curly brace | %7D | |
| `\ | | Pipe | %7C |
\ | Backslash | %5C | |
^ | Caret | %5E | |
`` | Backtick | %60 | |
| All non-ASCII | Unicode, emoji, accented characters | UTF-8 bytes, each encoded |
---
How Non-ASCII Characters Are Encoded
For characters outside ASCII (anything above U+007F), the character is first converted to its UTF-8 byte sequence, then each byte is percent-encoded individually.
For example, the Euro sign € has the Unicode code point U+20AC. Its UTF-8 representation is three bytes: 0xE2, 0x82, 0xAC. Encoded:
€ -> %E2%82%ACThe Japanese character 日 (U+65E5) encodes to three UTF-8 bytes:
日 -> %E6%97%A5Emoji follow the same pattern. The rocket emoji 🚀 (U+1F680) encodes to four bytes:
🚀 -> %F0%9F%9A%80This means that non-ASCII URLs like https://example.com/café become https://example.com/caf%C3%A9 in their wire format. Modern browsers display the decoded "pretty" version in the address bar, but the actual HTTP request uses the encoded form.
---
The Space Problem: %20 vs +
There are two ways to represent a space in a URL, and which one is correct depends on where in the URL you are.
In the Path
In URL path segments, spaces must always be encoded as %20:
/my documents/report.pdf -> /my%20documents/report.pdfUsing + in a path segment is incorrect. It will be treated as a literal plus sign, not a space.
In Query String Values
In query strings, both %20 and + are acceptable representations of a space. The + convention comes from the HTML form encoding specification (application/x-www-form-urlencoded), which pre-dates RFC 3986.
?q=hello world
?q=hello%20world <- RFC 3986 compliant
?q=hello+world <- application/x-www-form-urlencoded conventionThe distinction matters when decoding. A + in a query string decoded by an HTML form decoder becomes a space. But a + in a URL path does not - it remains a plus sign.
Best practice: use %20 everywhere and avoid + unless you are specifically working with HTML form encoding. It removes ambiguity and works consistently across all URL positions.
---
URL Encoding in JavaScript
JavaScript has three built-in functions for URL encoding and decoding:
encodeURI
Encodes a complete URL. It leaves reserved characters (:, /, ?, #, &, =, @) and unreserved characters unencoded because they have structural meaning in a complete URL.
encodeURI("https://example.com/path?q=hello world&lang=en");
// -> "https://example.com/path?q=hello%20world&lang=en"
// encodeURI does NOT encode these characters:
// : / ? # [ ] @ ! $ & ' ( ) * + , ; =Use encodeURI when you have an already-assembled URL and only need to encode the non-ASCII or unsafe characters.
encodeURIComponent
Encodes a single URL component (like a query parameter value). It encodes everything except the 66 unreserved characters.
encodeURIComponent("hello world & goodbye");
// -> "hello%20world%20%26%20goodbye"
encodeURIComponent("price=100€");
// -> "price%3D100%E2%82%AC"
// Build a query string safely
const params = {
q: "hello world",
category: "books & music",
price: "10-50€"
};
const queryString = Object.entries(params)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
// -> "q=hello%20world&category=books%20%26%20music&price=10-50%E2%82%AC"Use encodeURIComponent for encoding individual query parameter keys and values, path segments, and any data-derived strings going into a URL.
decodeURIComponent
Decodes a percent-encoded string back to its original form:
decodeURIComponent("hello%20world%20%26%20goodbye");
// -> "hello world & goodbye"
decodeURIComponent("%E2%82%AC");
// -> "€"The URLSearchParams API
For query string construction, the modern approach is to use the URLSearchParams API, which handles encoding automatically:
const params = new URLSearchParams({
q: "hello world",
category: "books & music",
page: "1"
});
console.log(params.toString());
// -> "q=hello+world&category=books+%26+music&page=1"
// Append to a base URL
const url = new URL("https://example.com/search");
url.search = params.toString();
console.log(url.href);
// -> "https://example.com/search?q=hello+world&category=books+%26+music&page=1"Note that URLSearchParams uses + for spaces (form encoding convention). If you need %20, use encodeURIComponent on each value manually.
The URL API
The URL class provides a structured way to build and parse URLs:
const url = new URL("https://example.com/search");
url.searchParams.set("q", "hello world");
url.searchParams.set("category", "books & music");
console.log(url.href);
// -> "https://example.com/search?q=hello+world&category=books+%26+music"
// Parsing an existing URL
const parsed = new URL("https://example.com/search?q=hello%20world&page=2");
console.log(parsed.searchParams.get("q")); // -> "hello world"
console.log(parsed.searchParams.get("page")); // -> "2"
console.log(parsed.pathname); // -> "/search"---
URL Encoding in Python
Python's urllib.parse module provides comprehensive URL encoding utilities:
from urllib.parse import quote, unquote, urlencode, quote_plus, unquote_plus, urlparse, parse_qs
# Encode a path segment (safe characters: slash not encoded by default)
quote("hello world/path")
# -> "hello%20world/path"
# Encode a path segment without allowing slash
quote("hello world/path", safe='')
# -> "hello%20world%2Fpath"
# Encode query parameter values
quote("hello world & goodbye", safe='')
# -> "hello%20world%20%26%20goodbye"
# Build a properly encoded query string
params = {
"q": "hello world",
"category": "books & music",
"page": 1
}
urlencode(params)
# -> "q=hello+world&category=books+%26+music&page=1"
# urlencode uses + for spaces; use quote_plus for the same behavior
quote_plus("hello world")
# -> "hello+world"
# Decode
unquote("hello%20world")
# -> "hello world"
unquote_plus("hello+world")
# -> "hello world"
# Parse a URL into components
parsed = urlparse("https://example.com/search?q=hello+world&page=2")
print(parsed.scheme) # https
print(parsed.netloc) # example.com
print(parsed.path) # /search
print(parsed.query) # q=hello+world&page=2
# Parse query parameters
params = parse_qs(parsed.query)
print(params) # {'q': ['hello world'], 'page': ['2']}---
URL Encoding in Go
Go's net/url package distinguishes between path encoding and query encoding:
package main
import (
"fmt"
"net/url"
)
func main() {
// Encode a path segment (%20 for spaces)
encoded := url.PathEscape("hello world/path")
fmt.Println(encoded) // hello%20world/path
// Encode a query parameter value (+ for spaces)
encoded = url.QueryEscape("hello world & goodbye")
fmt.Println(encoded) // hello+world+%26+goodbye
// Build a URL with query parameters
base := "https://example.com/search"
params := url.Values{}
params.Set("q", "hello world")
params.Set("category", "books & music")
fullURL := base + "?" + params.Encode()
fmt.Println(fullURL)
// https://example.com/search?category=books+%26+music&q=hello+world
// Parse a URL
parsed, _ := url.Parse("https://example.com/search?q=hello+world&page=2")
fmt.Println(parsed.Query().Get("q")) // hello world
}---
URL Encoding in Ruby, PHP, and Java
Ruby
require 'uri'
require 'cgi'
# Encode a URI component
URI.encode_www_form_component("hello world & goodbye")
# => "hello+world+%26+goodbye"
CGI.escape("hello world & goodbye")
# => "hello+world+%26+goodbye"
# Encode with %20 instead of +
URI.encode_uri_component("hello world")
# => "hello%20world"
# Build a query string
URI.encode_www_form([["q", "hello world"], ["page", "1"]])
# => "q=hello+world&page=1"PHP
// Encode a query parameter value (+ for spaces)
urlencode("hello world & goodbye");
// -> "hello+world+%26+goodbye"
// Encode with %20 for spaces
rawurlencode("hello world & goodbye");
// -> "hello%20world%20%26%20goodbye"
// Build a query string
$params = ['q' => 'hello world', 'page' => 1];
http_build_query($params);
// -> "q=hello+world&page=1"
// Decode
urldecode("hello+world"); // -> "hello world"
rawurldecode("hello%20world"); // -> "hello world"Java
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
// Encode
String encoded = URLEncoder.encode("hello world & goodbye", StandardCharsets.UTF_8);
// -> "hello+world+%26+goodbye"
// Decode
String decoded = URLDecoder.decode("hello+world", StandardCharsets.UTF_8);
// -> "hello world"
// Build a URI properly
URI uri = new URI("https", "example.com", "/search", "q=hello world", null);
System.out.println(uri.toString());
// -> "https://example.com/search?q=hello%20world"---
Complete Percent-Encoding Reference Table
The characters most commonly encountered in URL encoding:
| Character | Description | Encoded | |
|---|---|---|---|
| Space | %20 | |
! | Exclamation mark | %21 | |
" | Double quote | %22 | |
# | Hash | %23 | |
$ | Dollar sign | %24 | |
% | Percent sign | %25 | |
& | Ampersand | %26 | |
' | Single quote | %27 | |
( | Open parenthesis | %28 | |
) | Close parenthesis | %29 | |
* | Asterisk | %2A | |
+ | Plus | %2B | |
, | Comma | %2C | |
/ | Slash | %2F | |
: | Colon | %3A | |
; | Semicolon | %3B | |
< | Less than | %3C | |
= | Equals | %3D | |
> | Greater than | %3E | |
? | Question mark | %3F | |
@ | At sign | %40 | |
[ | Open bracket | %5B | |
\ | Backslash | %5C | |
] | Close bracket | %5D | |
^ | Caret | %5E | |
`` | Backtick | %60 | |
{ | Open brace | %7B | |
| `\ | | Pipe | %7C |
} | Close brace | %7D | |
~ | Tilde | %7E (unreserved, not required) | |
€ | Euro sign | %E2%82%AC | |
é | e with acute | %C3%A9 |
---
Common Mistakes and How to Avoid Them
Double Encoding
Double encoding happens when you encode a string that is already encoded:
Original: hello world
First pass: hello%20world <- correct
Second pass: hello%2520world <- brokenThe second pass encodes the % sign itself as %25, producing %2520 instead of %20. The browser then decodes it back to %20 instead of a space.
How to detect it: look for %25 in your URLs. If a URL contains %253A instead of %3A, or %2520 instead of %20, something was double-encoded.
Prevention: always decode before encoding if you are not sure of the input state, or verify that your encoding function is only called once on each value.
// Safe approach: always decode first, then encode
function safeEncode(value) {
try {
// If it decodes successfully, it might already be encoded
const decoded = decodeURIComponent(value);
// Only re-encode if something actually changed
if (decoded !== value) {
return encodeURIComponent(decoded);
}
} catch (e) {
// If decoding fails, it's not encoded
}
return encodeURIComponent(value);
}Encoding the Entire URL
Never encode a full URL as a single string with encodeURIComponent. It encodes the structural characters (://, /, ?, &, =) and breaks the URL completely:
// Wrong - completely breaks the URL structure
const url = encodeURIComponent("https://example.com/search?q=test");
// -> "https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dtest"
// This cannot be used as a URL
// Right - encode only the data portions
const base = "https://example.com/search";
const query = encodeURIComponent("hello world & goodbye");
const url = `${base}?q=${query}`;
// -> "https://example.com/search?q=hello%20world%20%26%20goodbye"Use encodeURIComponent for the values, not for the entire URL. Use encodeURI if you must encode an already-assembled URL.
Forgetting to Encode User Input
Any user-provided string that goes into a URL must be encoded. Otherwise, a malicious or simply careless user can break your URL structure or inject parameters:
User input: "test&admin=true"
Without encoding: /search?q=test&admin=true <- injected extra parameter
With encoding: /search?q=test%26admin%3Dtrue <- safeThis is both a functional bug (the injected admin=true may be read by your server) and a security concern in some applications. Always use encodeURIComponent on user-provided values before inserting them into URLs.
Using the Wrong Encoding Function
Different languages use different defaults for space encoding:
| Language / Function | Space Encoding |
|---|---|
JavaScript encodeURIComponent | %20 |
JavaScript URLSearchParams | + |
Python quote | %20 |
Python quote_plus / urlencode | + |
PHP rawurlencode | %20 |
PHP urlencode | + |
Go url.PathEscape | %20 |
Go url.QueryEscape | + |
If your encoder uses + for spaces but your decoder expects %20, you will get literal + signs in your decoded strings instead of spaces. Match the encoder and decoder conventions carefully.
Not Encoding Redirect URLs
When a URL contains another URL as a parameter value (common in login/redirect flows), the inner URL must be fully encoded:
// Wrong - the inner URL's ? and & will be parsed as part of the outer URL
const redirectUrl = "https://example.com/dashboard?tab=settings&view=profile";
const loginUrl = "https://auth.example.com/login?redirect=" + redirectUrl;
// -> Broken: the auth service sees ?redirect=https://example.com/dashboard&tab=settings...
// Right
const loginUrl = "https://auth.example.com/login?redirect=" + encodeURIComponent(redirectUrl);
// -> Correct: redirect value is properly encoded---
Security Implications of URL Encoding
Open Redirect Vulnerabilities
Improperly handling URL parameters that contain redirect targets can lead to open redirect vulnerabilities. An attacker may encode a redirect URL to bypass naive validation:
/login?redirect=%68%74%74%70%73%3A%2F%2Fattacker.com
^^ this is "https://attacker.com" encodedAlways decode and validate redirect URLs before using them. Check that the decoded URL belongs to your allowed domain list.
Path Traversal via Encoding
URL-encoding path traversal sequences like ../ can sometimes bypass naive security filters:
/files/%2E%2E%2F%2E%2E%2Fetc%2Fpasswd
^^ this is "../../etc/passwd" encodedProper URL parsing libraries decode paths before applying security rules, but incorrectly implemented servers may check the raw path before decoding. Always operate on decoded paths when applying security controls.
SQL Injection and XSS via URL Parameters
URL-encoded data that flows into database queries or HTML templates can carry injection payloads. URL decoding is just transport-layer safety - it does not sanitize the content. Always apply SQL parameterization and HTML escaping on decoded values.
---
How to Encode and Decode URLs With ToolBox
The URL Encoder/Decoder makes it simple to encode or decode any URL or URL component:
- Paste any text or URL into the input field
- Click Encode to see the percent-encoded version, or Decode to reverse it
- Switch between
encodeURIComponentmode (for values) andencodeURImode (for full URLs) - Copy the result with one click
The tool runs entirely in your browser. Your data is never sent to a server. This is important when encoding sensitive data like API keys, tokens, or internal URLs.
---
Related Tools
- URL Encoder/Decoder - Encode and decode URLs and URL components
- HTML Encoder - Encode HTML entities (
&,<, etc.) - different from URL encoding - Slug Generator - Convert text to URL-safe slugs for use in paths
- Base64 - Encode binary data as ASCII text (a different encoding scheme from percent-encoding)
- HTTP Request Builder - Build and test HTTP requests with properly encoded parameters
- Hash Generator - Generate URL-safe hashes
- Case Converter - Convert text casing before using it in URL slugs
---
Try It Now
Encode and decode URLs instantly in your browser:
URL Encoder/Decoder - Free, private, no signup required. Everything runs locally in your browser.
Related Tools
Free, private, no signup required
CSS Flexbox Generator
Flexbox generator - visual CSS flexbox layout builder with live preview and ready-to-copy CSS code
CSS Grid Generator
Free online CSS grid generator - visual CSS grid layout builder with live preview and code export
HTML to JSX Converter
Free online HTML to JSX converter - convert HTML markup to valid React JSX with automatic attribute and style transformations
Meta Tag Generator
Free online meta tag generator - generate SEO meta tags, Open Graph, and Twitter Card markup for your website
You might also like
Want higher limits, batch processing, and AI tools?