DevToolBox無料
ブログ

URL エンコーダ/デコーダ オンラインガイド:パーセントエンコーディング、RFC 3986、ベストプラクティス

14分by DevToolBox

URLエンコーダー/デコーダー:URLをオンラインでエンコード・デコードする完全ガイド

TL;DR
  • URLs can only contain ASCII characters; everything else must be percent-encoded (%XX).
  • Use encodeURIComponent() for individual query values, encodeURI() for full URLs.
  • Use URLSearchParams in JavaScript to build query strings safely — no manual encoding needed.
  • In Python, use urllib.parse.quote() for paths and urllib.parse.urlencode() for query strings.
  • In Go, use url.PathEscape() for path segments and url.QueryEscape() for query values.
  • Never double-encode: encoding an already-encoded string turns %20 into %2520.

1. URL Encoding Basics

A URL (Uniform Resource Locator) can only contain a restricted subset of ASCII characters. Characters outside this set — including spaces, non-ASCII characters, and many punctuation marks — must be percent-encoded before they can appear in a URL. Percent encoding, defined in RFC 3986, replaces each forbidden character with a % sign followed by two uppercase hexadecimal digits representing the character's byte value in ASCII or UTF-8.

For example, a space character (ASCII 32, or 0x20) becomes %20. An at-sign (@, ASCII 64, 0x40) becomes %40. For non-ASCII characters, the UTF-8 bytes are percent-encoded individually: the Chinese character 你 (U+4F60, UTF-8: 0xE4 0xBD 0xA0) becomes %E4%BD%A0.

RFC 3986: Reserved vs Unreserved Characters

RFC 3986 divides URL characters into two categories. Unreserved characters may appear in a URL without encoding. Reserved characters have a special syntactic purpose and must be encoded when used as data rather than as delimiters.

CategoryCharactersRule
UnreservedA-Z a-z 0-9 - _ . ~Never encode these — always safe
Reserved — general delimiters: / ? # [ ] @Encode when used as data, not as URL structure
Reserved — sub-delimiters! $ & ' ( ) * + , ; =Encode when used as data in query values
Other ASCIISpace, " < > \ ^ ` { | }Always encode — never allowed in raw form
Non-ASCIIAny byte > 0x7FUTF-8 encode, then percent-encode each byte

Common Encoded Characters Reference

CharacterEncodedNotes
Space%20In query strings, also encoded as + (form encoding)
+%2BLiteral plus sign — encode or it may be decoded as space
#%23Fragment start — always encode in query values
&%26Query separator — must encode in values
=%3DKey-value separator — encode in keys/values
?%3FQuery start — encode when in path
/%2FPath separator — encode in path segments
:%3AScheme/port separator — encode in query values
@%40User info delimiter — encode in query values
%%25Escape character itself — always encode
"%22Double quote — always encode
<%3CLess-than — always encode
>%3EGreater-than — always encode
[%5BArray notation — encode in most contexts
]%5DArray notation — encode in most contexts

2. encodeURI vs encodeURIComponent

JavaScript provides two built-in encoding functions that serve different purposes. Understanding when to use each is critical for correct URL handling.

encodeURI() — Encode a Full URL

encodeURI() is designed to encode a complete URL. It leaves untouched all characters that are valid parts of URL syntax: letters, digits, and the characters ; , / ? : @ & = + $ - _ . ! ~ * ' ( ) #. It only encodes characters that are never valid in a URL (spaces, non-ASCII, etc.).

encodeURIComponent() — Encode a URL Component

encodeURIComponent() is designed to encode a single component of a URL, such as a query parameter name or value. It encodes everything except unreserved characters: A-Z a-z 0-9 - _ . ~. This means it also encodes : / ? # [ ] @ ! $ & ' ( ) * + , ; =, which encodeURI() would preserve.

// Encoding: https://example.com?q=hello world&lang=zh

// encodeURI — preserves URL structure characters
encodeURI('https://example.com?q=hello world&lang=zh')
// → 'https://example.com?q=hello%20world&lang=zh'
// ✓ & and = are preserved (they are URL structure)
// ✓ :// is preserved

// encodeURIComponent — encodes everything including & = : /
encodeURIComponent('https://example.com?q=hello world&lang=zh')
// → 'https%3A%2F%2Fexample.com%3Fq%3Dhello%20world%26lang%3Dzh'
// The entire URL is treated as a value — not suitable for use as a URL

// Correct pattern: use encodeURIComponent on individual values
const base = 'https://example.com';
const query = '?q=' + encodeURIComponent('hello world') + '&lang=' + encodeURIComponent('zh');
// → 'https://example.com?q=hello%20world&lang=zh'

// Even better: use URLSearchParams (see Section 4)
const params = new URLSearchParams({ q: 'hello world', lang: 'zh' });
const url = base + '?' + params.toString();
// → 'https://example.com?q=hello+world&lang=zh'
FunctionUse caseDoes NOT encodeEncodes
encodeURI()Full URLA-Z a-z 0-9 ; , / ? : @ & = + $ - _ . ! ~ * ' ( ) #Spaces, non-ASCII, " < > { | } \ ^ `
encodeURIComponent()Single component (query value, path segment)A-Z a-z 0-9 - _ . ~Everything else, including : / ? # & = + @ , ;

3. decodeURI vs decodeURIComponent

Each encoding function has a matching decode function. The rule is symmetric: decode with the same function you used to encode.

// decodeURI — decodes only sequences that encodeURI would have encoded
decodeURI('https://example.com?q=hello%20world')
// → 'https://example.com?q=hello world'

// decodeURIComponent — decodes all percent-encoded sequences
decodeURIComponent('hello%20world%26lang%3Dzh')
// → 'hello world&lang=zh'

// Pitfall: using decodeURIComponent on a full URL
// will decode structural characters like %2F (/) which can break the URL
decodeURIComponent('https%3A%2F%2Fexample.com%2Fpath')
// → 'https://example.com/path'  ← technically correct here, but risky

// Double-encoding pitfall
const value = 'hello%20world'; // already encoded
encodeURIComponent(value)
// → 'hello%2520world'  ← %25 is encoded %, giving %2520 instead of %20

// Fix: always encode raw (decoded) values
const rawValue = 'hello world'; // raw, not yet encoded
encodeURIComponent(rawValue)
// → 'hello%20world'  ← correct

// If you receive an encoded value and need to re-encode it:
const incoming = 'hello%20world';
const reEncoded = encodeURIComponent(decodeURIComponent(incoming));
// → 'hello%20world'  ← idempotent

The double-encoding pitfall is one of the most common URL bugs. When you see %2520 in a URL, it almost always means a value was encoded twice. The server will decode it to %20 instead of a space.

4. JavaScript — URL and URLSearchParams

Modern JavaScript provides the URL constructor and URLSearchParams API, which handle encoding automatically and are the preferred approach for URL manipulation.

The URL Constructor

// URL constructor auto-encodes when you set properties
const url = new URL('https://example.com');
url.pathname = '/search results'; // has a space
console.log(url.pathname); // → '/search%20results'  (auto-encoded)
console.log(url.href);     // → 'https://example.com/search%20results'

// Parsing an existing URL
const parsed = new URL('https://example.com?q=hello%20world&page=2');
console.log(parsed.searchParams.get('q')); // → 'hello world'  (auto-decoded!)
console.log(parsed.hostname);  // → 'example.com'
console.log(parsed.pathname);  // → '/'
console.log(parsed.search);    // → '?q=hello%20world&page=2'  (encoded form)

// URL.canParse() — safe check without try/catch (modern browsers/Node 19+)
URL.canParse('https://example.com'); // → true
URL.canParse('not a url');           // → false

URLSearchParams — Building Query Strings

// Create from an object
const params = new URLSearchParams({ q: 'hello world', page: '1', lang: 'zh' });
console.log(params.toString());
// → 'q=hello+world&page=1&lang=zh'
// Note: URLSearchParams uses + for spaces (form encoding), not %20

// Append, set, delete
params.append('tag', 'javascript');
params.append('tag', 'webdev');  // supports multiple values for same key
params.set('page', '2');         // overwrites existing
params.delete('lang');
console.log(params.toString());
// → 'q=hello+world&page=2&tag=javascript&tag=webdev'

// Iterate
for (const [key, value] of params) {
  console.log(key, '=', value);
}

// Get all values for a key
params.getAll('tag'); // → ['javascript', 'webdev']

// Attach to a URL
const url = new URL('https://example.com/search');
url.search = params.toString();
console.log(url.href);
// → 'https://example.com/search?q=hello+world&page=2&tag=javascript&tag=webdev'

// Or use url.searchParams directly
const url2 = new URL('https://api.example.com/data');
url2.searchParams.set('query', 'hello world');
url2.searchParams.set('format', 'json');
console.log(url2.href);
// → 'https://api.example.com/data?query=hello+world&format=json'

5. Building Query Strings

Building query strings correctly is critical for API calls, form submissions, and link generation. There are several approaches with important differences.

Manual vs URLSearchParams

// BAD: Manual string concatenation (error-prone, no encoding)
const search = 'hello & world';
const badUrl = 'https://api.example.com?q=' + search;
// → 'https://api.example.com?q=hello & world'  ← invalid: unencoded &

// BETTER: Manual with encodeURIComponent
const betterUrl = 'https://api.example.com?q=' + encodeURIComponent(search);
// → 'https://api.example.com?q=hello%20%26%20world'  ← correct but verbose

// BEST: URLSearchParams handles encoding automatically
const url = new URL('https://api.example.com');
url.searchParams.set('q', search);
// → 'https://api.example.com?q=hello+%26+world'  ← correct, concise

Handling Arrays in Query Strings

// Arrays can be represented multiple ways — no single standard
const tags = ['javascript', 'webdev', 'react'];

// 1. Repeated keys (most common, supported by most frameworks)
// ?tags=javascript&tags=webdev&tags=react
const params1 = new URLSearchParams();
tags.forEach(tag => params1.append('tags', tag));
params1.toString(); // → 'tags=javascript&tags=webdev&tags=react'

// 2. Bracket notation (PHP / Laravel style)
// ?tags[]=javascript&tags[]=webdev&tags[]=react
const params2 = new URLSearchParams();
tags.forEach(tag => params2.append('tags[]', tag));
params2.toString(); // → 'tags%5B%5D=javascript&tags%5B%5D=webdev...'

// 3. Comma-separated (compact but less universal)
// ?tags=javascript,webdev,react
const params3 = new URLSearchParams({ tags: tags.join(',') });
params3.toString(); // → 'tags=javascript%2Cwebdev%2Creact'

// 4. JSON-encoded (for complex objects)
// ?filter={"tags":["javascript","webdev"]}
const params4 = new URLSearchParams({ filter: JSON.stringify({ tags }) });
params4.toString(); // → 'filter=%7B%22tags%22...%7D'

Handling Empty and Null Values

// Undefined and null handling
const filters = { status: 'active', category: null, page: 1, search: '' };

// Omit null/undefined, include empty strings
const params = new URLSearchParams();
Object.entries(filters).forEach(([key, value]) => {
  if (value !== null && value !== undefined) {
    params.set(key, String(value));
  }
});
params.toString(); // → 'status=active&page=1&search='

6. Python — urllib.parse

Python's standard library urllib.parse module provides comprehensive URL encoding and parsing utilities. No third-party libraries are needed for basic URL work.

from urllib.parse import (
    quote, quote_plus, unquote, unquote_plus,
    urlencode, urlparse, parse_qs, parse_qsl, urljoin
)

# --- Encoding ---

# quote(): percent-encode, safe="/" by default (preserves slashes)
quote('hello world/path')           # → 'hello%20world/path'
quote('hello world/path', safe='')  # → 'hello%20world%2Fpath'  (encode slashes too)
quote('café')                       # → 'caf%C3%A9'  (UTF-8 bytes)

# quote_plus(): like quote(), but spaces become + (for form/query data)
quote_plus('hello world&more')      # → 'hello+world%26more'
quote_plus('hello world', safe='')  # → 'hello+world'

# --- Decoding ---
unquote('hello%20world')            # → 'hello world'
unquote_plus('hello+world%26more')  # → 'hello world&more'

# --- Building query strings ---
params = {'q': 'hello world', 'page': 1, 'tag': ['python', 'webdev']}

# Single values
urlencode({'q': 'hello world', 'page': 1})
# → 'q=hello+world&page=1'

# Multiple values for same key (doseq=True)
urlencode({'tag': ['python', 'webdev']}, doseq=True)
# → 'tag=python&tag=webdev'

# --- Parsing URLs ---
result = urlparse('https://user:pass@example.com:8080/path?q=hello&page=2#section')
result.scheme    # → 'https'
result.netloc    # → 'user:pass@example.com:8080'
result.hostname  # → 'example.com'
result.port      # → 8080
result.path      # → '/path'
result.query     # → 'q=hello&page=2'
result.fragment  # → 'section'

# --- Parsing query strings ---
parse_qs('q=hello+world&page=2&tag=a&tag=b')
# → {'q': ['hello world'], 'page': ['2'], 'tag': ['a', 'b']}
# Note: parse_qs always returns lists

parse_qsl('q=hello+world&page=2')
# → [('q', 'hello world'), ('page', '2')]  (ordered list of tuples)

# --- Building full URLs ---
from urllib.parse import urlencode, urlparse, urlunparse
base = 'https://api.example.com/search'
query = urlencode({'q': 'hello world', 'limit': 10})
full_url = f'{base}?{query}'
# → 'https://api.example.com/search?q=hello+world&limit=10'

7. Go — net/url

Go's net/url package provides robust URL parsing and encoding. Unlike JavaScript, Go distinguishes between path escaping and query escaping with separate functions.

package main

import (
    "fmt"
    "net/url"
)

func main() {
    // --- Path encoding ---
    // url.PathEscape: encodes a path segment, spaces → %20
    fmt.Println(url.PathEscape("hello world"))    // → "hello%20world"
    fmt.Println(url.PathEscape("café/résumé"))    // → "caf%C3%A9%2Fr%C3%A9sum%C3%A9"
    // Note: PathEscape encodes / as %2F within a segment

    // url.PathUnescape: decodes path segments
    s, _ := url.PathUnescape("hello%20world")
    fmt.Println(s) // → "hello world"

    // --- Query encoding ---
    // url.QueryEscape: encodes for query params, spaces → +
    fmt.Println(url.QueryEscape("hello world"))   // → "hello+world"
    fmt.Println(url.QueryEscape("a&b=c"))         // → "a%26b%3Dc"

    // url.QueryUnescape: decodes query params
    s2, _ := url.QueryUnescape("hello+world%26more")
    fmt.Println(s2) // → "hello world&more"

    // --- Building URLs with url.Values ---
    v := url.Values{}
    v.Set("q", "hello world")
    v.Set("page", "1")
    v.Add("tag", "golang")
    v.Add("tag", "webdev")
    fmt.Println(v.Encode()) // → "page=1&q=hello+world&tag=golang&tag=webdev"
    // Note: keys are sorted alphabetically

    // --- Parsing URLs ---
    u, err := url.Parse("https://example.com/search?q=hello+world&page=2#top")
    if err != nil {
        panic(err)
    }
    fmt.Println(u.Scheme)               // → "https"
    fmt.Println(u.Host)                 // → "example.com"
    fmt.Println(u.Path)                 // → "/search"
    fmt.Println(u.RawQuery)             // → "q=hello+world&page=2"
    fmt.Println(u.Fragment)             // → "top"
    fmt.Println(u.Query().Get("q"))     // → "hello world"  (auto-decoded)
    fmt.Println(u.Query()["tag"])       // → []  (or all values for "tag")

    // --- Building a URL from parts ---
    base := &url.URL{
        Scheme: "https",
        Host:   "api.example.com",
        Path:   "/v1/search results",  // has a space
    }
    params := url.Values{"q": {"hello world"}, "limit": {"10"}}
    base.RawQuery = params.Encode()
    fmt.Println(base.String())
    // → "https://api.example.com/v1/search%20results?limit=10&q=hello+world"

    // --- Joining URLs ---
    ref, _ := url.Parse("../other-page")
    result := u.ResolveReference(ref)
    fmt.Println(result.String())
}

8. Form Data Encoding

HTML forms can submit data in different encodings depending on the enctype attribute. Understanding these formats is essential for building APIs and handling form submissions.

application/x-www-form-urlencoded

This is the default encoding for HTML forms (when method="POST" or method="GET" without a file upload). Key characteristics:

  • Spaces are encoded as + (not %20)
  • Key-value pairs separated by &
  • Keys and values separated by =
  • All other special characters are percent-encoded
<!-- Default form encoding — application/x-www-form-urlencoded -->
<form method="POST" action="/search">
  <input name="q" value="hello world" />
  <input name="lang" value="zh" />
</form>
<!-- POST body: q=hello+world&lang=zh -->

<!-- For GET forms, same encoding but in the URL query string -->
<form method="GET" action="/search">
  <input name="q" value="hello world" />
</form>
<!-- URL: /search?q=hello+world -->

// Equivalent in JavaScript with fetch():
const formData = new URLSearchParams({ q: 'hello world', lang: 'zh' });
fetch('/search', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: formData.toString(),
  // body: 'q=hello+world&lang=zh'
});

multipart/form-data

Required for file uploads (<input type="file" />). Each field is a separate MIME part with its own headers. Values are NOT percent-encoded; the binary data is transmitted as-is within boundaries.

application/json

Modern REST APIs commonly accept JSON bodies instead of form encoding. JSON handles all character escaping internally via backslash escaping, not percent-encoding.

Encoding typeSpace encoded asFile uploadBest for
application/x-www-form-urlencoded+NoSimple HTML forms, small data
multipart/form-dataLiteral (in boundaries)YesFile uploads, binary data
application/jsonLiteral (in JSON strings)No (use base64)REST APIs, structured data

9. URL Parts Reference

A URL has several distinct parts, each with different encoding rules. Understanding which part needs which encoding prevents common mistakes.

// Full URL anatomy:
// https://user:password@example.com:8080/path/to/page?key=value&k2=v2#section
//   |       |      |         |       |   |              |                  |
//  scheme  user  password  host    port path           query            fragment

// In JavaScript, the URL object exposes each part:
const url = new URL('https://user:pass@example.com:8080/path/to?k=v#frag');
url.protocol   // → 'https:'
url.username   // → 'user'   (decoded)
url.password   // → 'pass'   (decoded)
url.hostname   // → 'example.com'
url.port       // → '8080'
url.host       // → 'example.com:8080'
url.pathname   // → '/path/to'  (decoded)
url.search     // → '?k=v'      (encoded, includes ?)
url.searchParams.get('k')  // → 'v'  (decoded)
url.hash       // → '#frag'     (encoded, includes #)
url.href       // → full URL string (encoded)
URL PartExampleEncoding rule
SchemehttpsAlways lowercase ASCII letters, digits, + - . — no encoding needed
Username / Passworduser:passEncode with encodeURIComponent(); avoid using in URLs
Host (ASCII)example.comNo encoding; use Punycode for non-ASCII
Port8080Digits only — no encoding
Path segment/path/to page/Encode each segment with encodeURIComponent() or url.PathEscape(); do NOT encode the / separators
Query key/value?q=hello worldEncode with encodeURIComponent() or URLSearchParams
Fragment#section idEncode with encodeURIComponent(); never sent to server

10. Internationalized URLs (IDN)

The original URL specification only allowed ASCII characters. As the internet became global, two standards emerged to handle non-ASCII in URLs: Punycode for domain names and percent-encoding of UTF-8 bytes for paths and queries.

Punycode for Non-ASCII Domains

Internationalized domain names (IDN) use Punycode encoding (RFC 3492) to represent Unicode characters using ASCII-compatible encoding (ACE). The encoded domain starts with the prefix xn--.

// Punycode examples:
// münchen.de  →  xn--mnchen-3ya.de
// 日本語.jp    →  xn--wgv71a309e.jp
// مثال.إختبار →  xn--mgbh0fb.xn--kgbechtv

// In the browser: you type münchen.de, the browser sends xn--mnchen-3ya.de
// The browser displays the Unicode form in the address bar for readability

// JavaScript — URL API handles IDN automatically
const url = new URL('https://münchen.de/path');
console.log(url.hostname);   // → 'xn--mnchen-3ya.de'  (wire format)
console.log(url.host);       // → 'xn--mnchen-3ya.de'
console.log(url.href);       // → 'https://xn--mnchen-3ya.de/path'

// Node.js — check if URL is parseable
URL.canParse('https://münchen.de');  // → true (Node 19+)

// Python — encode IDN domains
import encodings.idna
'münchen'.encode('idna').decode('ascii')  # → 'xn--mnchen-3ya'

Unicode Path Segments

Unlike domains, non-ASCII path segments and query values use UTF-8 percent-encoding. Each byte of the UTF-8 encoding is represented as %XX.

// Unicode in paths and queries uses UTF-8 percent-encoding
// 'résumé' in UTF-8:
//   r  →  72   →  r
//   é  →  0xC3 0xA9  →  %C3%A9
//   s  →  73   →  s
//   u  →  75   →  u
//   m  →  6D   →  m
//   é  →  0xC3 0xA9  →  %C3%A9

encodeURIComponent('résumé')  // → 'r%C3%A9sum%C3%A9'
encodeURIComponent('你好世界') // → '%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C'

// Modern browsers display the decoded form in address bar
// Wire format: GET /%E4%BD%A0%E5%A5%BD HTTP/1.1
// Display:     example.com/你好

// Python equivalent
from urllib.parse import quote
quote('résumé')   # → 'r%C3%A9sum%C3%A9'
quote('你好世界')  # → '%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C'

11. API Design — URL Encoding Best Practices

These practices prevent encoding bugs in production systems.

Always Use URLSearchParams Over Manual Concatenation

// NEVER do this:
const query = '?search=' + userInput + '&page=' + page;
// If userInput = "cats & dogs", the URL breaks

// ALWAYS do this:
const params = new URLSearchParams({ search: userInput, page: String(page) });
const query = '?' + params.toString();
// userInput = "cats & dogs" → ?search=cats+%26+dogs&page=1  ✓

Log Decoded URLs for Readability

// For logging, decode the URL for human readability
const rawUrl = req.url; // may contain %20, %26, etc.
const displayUrl = decodeURIComponent(rawUrl);
console.log('Request:', displayUrl); // shows readable form

Store Decoded Values in Database

// Decode URL parameters before storing in DB
// The DB should store the actual value, not the encoded form
app.get('/search', (req, res) => {
  // req.query.q is already decoded by Express
  const query = req.query.q; // 'hello world', not 'hello%20world'
  db.save({ searchTerm: query }); // store decoded
});

Validate Before Decoding

// Always validate that the encoded string is valid before decoding
function safeDecodeURIComponent(str: string): string | null {
  try {
    return decodeURIComponent(str);
  } catch {
    // URIError: malformed percent encoding
    return null;
  }
}

safeDecodeURIComponent('hello%20world'); // → 'hello world'
safeDecodeURIComponent('hello%ZZworld'); // → null (invalid hex)
safeDecodeURIComponent('%E4%BD');        // → null (incomplete UTF-8)

Never Trust Decoded User Input Without Sanitization

// A user can encode a malicious payload:
// ?redirect=%2F%2Fevil.com%2Fphish  (decoded: //evil.com/phish)

// Always validate after decoding
function safeRedirect(url: string): string {
  const decoded = decodeURIComponent(url);
  // Only allow relative URLs (start with /) or known domains
  if (decoded.startsWith('/') && !decoded.startsWith('//')) {
    return decoded;
  }
  return '/'; // fallback to home
}

12. Common Mistakes

Double Encoding (%2520)

// Problem: encoding an already-encoded string
const value = encodeURIComponent('hello world'); // → 'hello%20world'
const doubled = encodeURIComponent(value);       // → 'hello%2520world'
// %25 is the encoding of %, so %20 → %2520

// Fix: only encode raw (decoded) values
const raw = 'hello world';
const safe = encodeURIComponent(raw); // → 'hello%20world'  ✓

Using + for Space in Path (Only Valid in Query)

// + means a literal plus sign in a URL path
// It only means space in application/x-www-form-urlencoded query strings

// WRONG: /search+results  (might be interpreted as "/search+results" literally)
// RIGHT: /search%20results  ✓

// In query string, both are usually accepted:
// /search?q=hello+world  ✓  (form encoding)
// /search?q=hello%20world  ✓  (percent encoding)

Encoding Slashes in Paths

// Never encode the / separators in a URL path
// WRONG:
'https://example.com' + encodeURIComponent('/path/to/page')
// → 'https://example.com%2Fpath%2Fto%2Fpage'  — browser may reject this

// RIGHT: encode each segment individually
const segments = ['path', 'to', 'my page'];
const path = '/' + segments.map(encodeURIComponent).join('/');
// → '/path/to/my%20page'  ✓

Forgetting to Decode Before Display

// Never display raw encoded URLs to users
const url = '/search?q=hello%20world%20%26%20more';

// BAD:
document.title = 'Search: ' + new URL(url, location.origin).searchParams.get('q');
// If not decoded: "Search: hello%20world%20%26%20more"

// GOOD:
const q = new URL(url, location.origin).searchParams.get('q');
// URLSearchParams auto-decodes: q = 'hello world & more'
document.title = 'Search: ' + q; // ✓

XSS via URL Injection

// URL parameters can contain XSS payloads after decoding
// ?name=<script>alert(1)</script>
// Decoded: name = '<script>alert(1)</script>'

// NEVER insert decoded URL params directly into HTML:
// document.innerHTML = '<p>Hello ' + req.query.name + '</p>';  // XSS!

// ALWAYS escape HTML entities:
function escapeHtml(str: string): string {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;');
}
// Or use a trusted HTML sanitizer library

Frequently Asked Questions

What is URL encoding (percent encoding)?

URL encoding (percent encoding) replaces characters that cannot appear in a URL with a % sign followed by two hexadecimal digits representing the byte value. For example, a space (byte 0x20) becomes %20. It is defined in RFC 3986 and ensures URLs only contain valid ASCII.

What is the difference between encodeURI() and encodeURIComponent()?

encodeURI() encodes a full URL and preserves URL structure characters like : / ? # & = + @ ,. encodeURIComponent() encodes a single URL component (a query value or path segment) and encodes everything except A-Z a-z 0-9 - _ . ~. Use encodeURIComponent() for individual parameter values.

Why do I see %2520 in my URL instead of %20?

This is double encoding. You encoded an already-encoded string. The % in %20 got encoded to %25, producing %2520. Fix: only encode raw (not yet encoded) strings. If you receive an encoded value, decode it first with decodeURIComponent() before re-encoding.

Should I use %20 or + for spaces in URLs?

Use %20 in URL paths — always. In query strings, both %20 and + are usually accepted by servers, but URLSearchParams uses + (form encoding convention). In URL paths, + is treated as a literal plus sign, not a space.

How do I build a safe query string in JavaScript?

Use URLSearchParams: new URLSearchParams({ q: "hello world", page: "1" }).toString(). This auto-encodes all values. For URL construction, use the URL constructor: const url = new URL("https://example.com"); url.searchParams.set("q", "hello world"); url.toString().

How do I URL-encode in Python?

Use urllib.parse: quote("hello world") → "hello%20world" for paths; quote_plus("hello world") → "hello+world" for query strings; urlencode({"q": "hello world", "page": 1}) → "q=hello+world&page=1" for building complete query strings.

What is the difference between QueryEscape and PathEscape in Go?

url.QueryEscape() encodes for query params (spaces as +), while url.PathEscape() encodes for path segments (spaces as %20). Use PathEscape for URL paths and QueryEscape (or url.Values.Encode()) for query string values.

How are non-ASCII domains like münchen.de encoded?

Non-ASCII domains use Punycode encoding (RFC 3492). münchen.de becomes xn--mnchen-3ya.de. Browsers display the Unicode form in the address bar but send the Punycode form over the network. Non-ASCII path segments and query values use UTF-8 percent-encoding instead.

Key Takeaways
  • RFC 3986 unreserved chars (A-Z a-z 0-9 - _ . ~) never need encoding.
  • Use encodeURIComponent() for individual query values and path segments; use encodeURI() for complete URLs.
  • URLSearchParams is the safest way to build query strings in JavaScript — no manual encoding required.
  • In Python, urllib.parse.urlencode() builds query strings; urllib.parse.quote() encodes path segments.
  • In Go, url.PathEscape() for paths (space → %20), url.QueryEscape() for queries (space → +).
  • Spaces in paths must be %20; + for spaces is only valid in application/x-www-form-urlencoded query strings.
  • Double encoding (%2520) happens when you encode an already-encoded string; always encode raw values.
  • Non-ASCII domains use Punycode; non-ASCII path/query values use UTF-8 percent-encoding.
  • Always sanitize decoded URL parameters before using them in HTML to prevent XSS.
𝕏 Twitterin LinkedIn
この記事は役に立ちましたか?

最新情報を受け取る

毎週の開発ヒントと新ツール情報。

スパムなし。いつでも解除可能。

Try These Related Tools

%20URL Encoder/Decoder🔗URL ParserB64Base64 Encoder/DecoderB→Base64 Encoder

Related Articles

URLエンコード特殊文字:完全リファレンステーブル&例

URLパーセントエンコーディングの完全リファレンス。全特殊文字の一覧表、encodeURIComponentとencodeURIの使い分け。

URLエンコード&デコード完全ガイド:パーセントエンコーディング解説

無料のURLエンコーダー&デコーダー。JavaScript、Python、Bash、PHPでのエンコード方法を詳しく解説。

URLエンコーディング(パーセントエンコーディング)解説:%20と%3Aの本当の意味

URLパーセントエンコーディングのバイトレベルの仕組み。文字参照表、encodeURIとencodeURIComponentの違い、各言語のコード例。