URL and percent-encoding: characters, query parameters, and examples
URL anatomy, RFC 3986 reserved characters, UTF-8 percent-encoding, query parameters, and URLSearchParams examples.
A URL is a set of components
The same character can be syntax in one component and ordinary data in another. Encode the value at the boundary where it is inserted.
- Scheme
httpsSelects the protocol. - Host
example.comNames the server; domains are serialized separately from path text. - Port
443Optional network port after the host. - Path
/catalog/caf%C3%A9A sequence of slash-separated segments. - Query
?q=coffee&tag=urlApplication-defined name/value data after ?. - Fragment
#examplesClient-side position or state after #; normally not sent in HTTP.
Reserved characters and their percent form
A reserved character may remain literal when it performs its defined delimiter role. When it is data inside a component, encode it so it cannot change the URL structure.
No matching reserved characters.
| Character | Group | HEX | Percent | Typical role |
|---|---|---|---|---|
:Colon | gen-delims | 3A | Separates the scheme, port, and userinfo parts. | |
/Slash | gen-delims | 2F | Separates path segments; two slashes introduce the authority. | |
?Question mark | gen-delims | 3F | Starts the URL query component. | |
#Number sign | gen-delims | 23 | Starts the fragment; it is normally not sent to the server. | |
[Left square bracket | gen-delims | 5B | Opens an IPv6 literal in the host. | |
]Right square bracket | gen-delims | 5D | Closes an IPv6 literal in the host. | |
@At sign | gen-delims | 40 | Separates userinfo from the host. | |
!Exclamation mark | sub-delims | 21 | A sub-delimiter whose exact meaning is scheme- or component-specific. | |
$Dollar sign | sub-delims | 24 | A sub-delimiter that can have scheme-specific meaning. | |
&Ampersand | sub-delims | 26 | Commonly separates query parameter pairs. | |
'Apostrophe | sub-delims | 27 | A sub-delimiter without one universal meaning. | |
(Left parenthesis | sub-delims | 28 | A sub-delimiter without one universal meaning. | |
)Right parenthesis | sub-delims | 29 | A sub-delimiter without one universal meaning. | |
*Asterisk | sub-delims | 2A | A sub-delimiter; URLSearchParams normally leaves it as *. | |
+Plus sign | sub-delims | 2B | Represents a space in form encoding; encode a literal plus as %2B. | |
,Comma | sub-delims | 2C | A sub-delimiter whose meaning depends on the API or scheme. | |
;Semicolon | sub-delims | 3B | A sub-delimiter sometimes used for path-segment parameters. | |
=Equals sign | sub-delims | 3D | Commonly separates a query parameter name from its value. |
Percent-encoding examples
First turn the character into UTF-8 bytes, then write every encoded byte as % followed by two hexadecimal digits.
| Input | UTF‑8 | Encoded | Why |
|---|---|---|---|
SPSpace | 20 | Becomes + in URLSearchParams. | |
+Literal plus | 2B | Must be distinguished from a form-encoded space. | |
&Ampersand in a value | 26 | Otherwise it looks like a parameter separator. | |
=Equals sign in a value | 3D | Otherwise it can look like a name/value boundary. | |
/Slash as data | 2F | Inside one component it must not split the path. | |
#Number sign as data | 23 | Otherwise it starts the fragment. | |
%Percent sign | 25 | Starts a percent triplet and must itself be encoded. | |
éLatin é | C3 A9 | Two UTF-8 bytes produce two triplets. | |
яCyrillic ya | D1 8F | Encoded byte by byte in UTF-8. | |
😀Emoji | F0 9F 98 80 | Four UTF-8 bytes produce four triplets. |
Build query parameters as tuples
Do not concatenate user data with &, =, and + by hand. URLSearchParams preserves the name/value boundary and repeated keys.
const url = new URL("https://example.com/search");
url.searchParams.set("q", "кофе & чай");
url.searchParams.append("tag", "url");
url.searchParams.append("tag", "encoding");
url.toString(); params.get("tag")returns only the first value
params.getAll("tag")returns ["url", "encoding"]
params.has("debug")checks presence separately from its value
decodeURIComponent("a+b")returns a+b, not a space; use URLSearchParams for form-style query parsing
Which browser API to use
Parse, resolve, and serialize a complete URL. Set pathname, searchParams, and hash through their dedicated properties.
const url = new URL("/search", "https://example.com");Build or read query tuples. It uses application/x-www-form-urlencoded: spaces become + and literal plus signs become %2B.
new URLSearchParams({ q: "coffee & tea" }).toString()Encode text that will become one URL component. A space becomes %20; do not apply it to an already assembled URL.
encodeURIComponent("a/b?c=d") // a%2Fb%3Fc%3DdKeeps URI delimiters such as /, ?, #, &, and =. It does not make an untrusted query value safe for concatenation.
encodeURI("https://example.com/a b?q=x")Standards behind the tables
Continue working with text
Copy a symbol, convert markup, clean a fragment, or keep the result in the notebook.
- Encode HTML entities Turn special characters into safe HTML entities or decode them back.
- Convert Markdown to HTML Prepare HTML markup from a Markdown draft.
- Remove HTML tags Keep readable text and discard markup.
- Change text case Convert letters to upper or lower case.
- Open the online notebook Use copied symbols in a note stored locally in your browser.
Questions about URL encoding
Why does a space sometimes become %20 and sometimes +?
%20 is the percent-encoded UTF-8 byte for a space. The application/x-www-form-urlencoded format used by URLSearchParams serializes spaces as +. Both conventions occur in query strings, but + is not a general replacement for spaces in every URL component.
How do I put a literal plus sign in a query value?
Use URLSearchParams or encode it as %2B. A form-style query parser interprets a raw + as a space.
Should I encode a whole URL with encodeURIComponent()?
No. It encodes structural delimiters and turns a whole URL into one component. Use new URL() for the whole URL and URLSearchParams or encodeURIComponent() only for the specific value boundary.
Can query parameters repeat?
Yes. A query is an ordered list of name/value tuples, so tag=url&tag=encoding is valid. Use append() to add values and getAll() to read every repeated value.
Should percent triplets use uppercase hex?
Hex digits are case-insensitive, but RFC 3986 recommends uppercase A–F for normalization, such as %2F rather than %2f.