Asking whether a character needs URL encoding has no single answer, because the correct behaviour depends on where in the address the character appears. A slash is structure inside a path and data inside a query value. A question mark opens the query string once and is ordinary text afterwards.
This reference works through the characters that cause real problems, grouped by what they do. The organising principle throughout is simple: encode a character whenever it appears inside a value, and leave it alone when it is acting as structure.
Characters that never need encoding
The unreserved set is safe everywhere: the letters A to Z and a to z, the digits 0 to 9, and the four symbols hyphen, period, underscore, and tilde. Encoders should leave these untouched, and a normalising step may convert %7E back to ~ because the two forms are defined as equivalent.
This is why hyphens rather than spaces or underscores are the conventional word separator in readable URLs. A hyphen survives every encoding stage unchanged, stays legible in a link, and never introduces an escape sequence into a path that people will read or share.
URL encode space: the two different forms
A space is never legal in a URL and always requires encoding. In paths and query strings it becomes %20. In an application/x-www-form-urlencoded body it becomes +, a convention inherited from early HTML forms that persists in every form-handling library.
The ambiguity this creates is worth stating plainly: a literal plus sign in a value must be written as %2B, or a form decoder will read it as a space. This affects passwords, search terms, and any identifier that legitimately contains the character.
URL encode ampersand and other query delimiters
The ampersand separates parameters and the equals sign separates a key from its value, so both must be encoded inside a value: %26 and %3D. An unencoded ampersand in a product name is the classic cause of a truncated parameter, because everything after it is parsed as the next pair.
The question mark opens the query string. Once inside it, a question mark is ordinary data, and most parsers accept it literally, but encoding it as %3F is safer across proxies and log processors. The hash character is stricter: it always begins the fragment, so a literal one must be %23 without exception.
Path delimiters and the colon
The slash separates path segments, so a value containing one must use %2F, otherwise a single identifier becomes two segments and routing breaks. Note that some servers reject or normalise encoded slashes for security reasons, which is a good argument against putting them in identifiers at all.
The colon separates the scheme from the rest of the address and a host from a port. Inside a path segment or a query value it is generally accepted literally, but %3A is the portable choice. The at sign has a similar history from user information in authority components, and encoding it as %40 avoids ambiguity in an email address used as a path parameter.
Brackets, braces, and other sub-delimiters
Square brackets are reserved for IPv6 literals in the host component, so a value containing them should use %5B and %5D. This matters for the array parameter convention that many frameworks use, where a key looks like filter[status] and different servers disagree about whether the brackets may be literal.
Curly braces, the pipe, the backslash, the caret, and the backtick are not reserved but are not permitted literally either. Encode them as %7B, %7D, %7C, %5C, %5E, and %60. The backslash deserves particular care because some browsers historically normalised it to a forward slash, which has been the basis of real redirect vulnerabilities.
Apostrophes, commas, asterisks, and parentheses
These sit in an awkward middle ground. The apostrophe, comma, asterisk, exclamation mark, and parentheses are technically permitted in many positions, and JavaScript's encodeURIComponent() deliberately leaves several of them alone. That is legal but frequently inconvenient.
The comma is the most troublesome, because many APIs treat it as a list separator inside a single parameter. If a value can contain a comma, encode it as %2C explicitly. Apostrophes in names are worth encoding as %27 for a different reason: they interact badly with log parsers and with HTML attributes that quote a URL with single quotes.
Non-ASCII text and internationalised domains
Characters outside ASCII are encoded as their UTF-8 bytes, so each one produces multiple escapes. Cyrillic, Greek, and CJK characters typically need two or three sequences each, and an emoji needs four. This is why a readable non-English URL becomes a long escape string when copied out of a browser.
Domain names follow a different mechanism entirely. Internationalised domains use Punycode, converting a name to an ASCII form beginning with xn--, rather than percent-encoding. Applying percent escapes to a hostname produces an address that will not resolve.
URL encoding cheat sheet table
This is the percent encoding table in its compact form. The final column is the one that matters: it says when the character has to be escaped rather than simply whether it can be.
| Character | Encoded | When it must be encoded |
|---|---|---|
| space | %20 or + | Always. URL encode a space as %20; + only in form bodies |
| & | %26 | Always inside a value. URL encode ampersand or the parameter splits |
| = | %3D | Inside a value |
| ? | %3F | Inside a value; opens the query once |
| # | %23 | Always. It otherwise begins the fragment |
| / | %2F | Inside a path segment or a value |
| : | %3A | URL encode colon in a value; literal is often accepted |
| @ | %40 | URL encode at sign in a path or value |
| , | %2C | URL encode comma when an API treats it as a list separator |
| ' | %27 | URL encode apostrophe for logs and quoted attributes |
| * | %2A | URL encode asterisk; legal but often reserved by APIs |
| [ ] | %5B %5D | URL encode brackets outside an IPv6 host |
| { } | %7B %7D | Always; not permitted literally |
| \ | %5C | Always. URL encode backslash; some clients rewrite it to / |
| | | %7C | Always |
| + | %2B | Always in a value, or a form decoder reads it as a space |
| % | %25 | Always. Missing this is what causes double encoding |
| " | %22 | Always |
| < > | %3C %3E | Always |
| - | none | Never. URL encode dash is unnecessary; it is unreserved |
| . _ ~ | none | Never; unreserved like the hyphen |
| non-ASCII | UTF-8 bytes | Always; each character becomes several escapes |
The awkward middle group is worth naming explicitly, because engines disagree about it. URL encoding asterisk, URL encoding brackets and URL encoding backslash characters are all safest escaped even where a specification permits them literally, and the same caution applies to URL encoding ampersand characters inside any value. To URL encode all characters that are not unreserved, use a component encoder rather than a whole-URL encoder, since only the former escapes delimiters.
Percent encoding URL syntax is the mechanism behind every row here: a percent encoding decoder reverses the table, and running percent encoding to text form is what a URL decode characters step does. A percent encoding online panel and a URL encoding converter are the same operation presented two ways.
Two cases deserve their own note. To URL encode password values for a connection string, encode it as a component so that @ and : cannot be mistaken for the delimiters around user information. And a URL encoder for SVG used in a CSS url() value needs #, <, >, and quotes escaped, but deliberately leaves spaces readable, which is why dedicated SVG encoders exist rather than a plain component encoder.
URL encoding abuse and attack attempts
Encoding is also an evasion technique. A URL encoding abuse attack attempt typically hides a payload from a filter that inspects the raw string: %2e%2e%2f becomes ../ after decoding, and double-encoded forms such as %252e survive one decode pass to reappear later.
The defence is ordering rather than pattern matching. Decode exactly once at a known boundary, then validate the decoded value, and reject input that still contains percent sequences afterwards. Filters that run before decoding inspect a string the application will never actually use.
Form bodies, content types, and Base64 in URLs
A URL encoded body is a request whose payload is a query string, declared by the URL encoded content type application/x-www-form-urlencoded. The distinguishing rule is the plus sign: in this format it means a space, so a literal plus in a value must be sent as %2B.
The same conflict explains why standard Base64 travels badly in an address. To URL encode Base64 output you must escape +, /, and =, which inflates the string; URL encoding Base64 this way works but is why the URL-safe Base64 variant exists, substituting - and _ so no escaping is needed at all.
Using the table in practice
Rather than memorising individual codes, apply the context rule and let a proper encoder do the work. Encode each query key and value as a component, encode each path segment separately, and never encode an assembled URL as though it were a single value.
Keep a decoder to hand for inspection. Pasting a suspicious link into a URL decoder reveals double encoding, unexpected delimiters, and hidden traversal sequences immediately, and it is far quicker than reading a long escape string by eye.
Primary sources: RFC 3986 defines URI syntax and percent-encoding, and the WHATWG URL Standard describes what browsers actually implement.