DevTools Hub

Search tools

Search for a developer tool

What Characters Need URL Encoding?

Part of the Encoding Toolkit

"Which characters actually need encoding?" is a narrower question than how percent-encoding works, and it deserves a direct, scannable answer — not everyone hitting this page wants the mechanism, some just want to check whether @ is safe to leave alone. Here's the reference.

The short list that's always safe

Per RFC 3986, exactly these characters are unreserved — never encoded, in any part of a URL, by any tool that follows the spec:

A–Z  a–z  0–9  -  _  .  ~

Everything else is either reserved (has structural meaning somewhere in a URL, so whether it needs encoding depends on where it sits) or falls outside ASCII entirely (always needs encoding, no exceptions).

The reserved characters, and what each one means

RFC 3986 splits reserved characters into two groups. gen-delims mark the major structural boundaries of a URL:

CharacterMarks
:Scheme separator (https:), and port (host:port)
/Path segment separator
?Start of the query string
#Start of the fragment
[ ]Wrap an IPv6 host literal
@Separates userinfo from the host

sub-delims are reserved for use within a single URL component — most commonly seen doing double duty in query strings:

CharacterTypical meaning
&Separates parameters in a query string
=Separates a parameter's key from its value
+Means a literal space, in a query string specifically (see below)
;Historically an alternate parameter separator in some systems
! $ ' ( ) * ,No universal structural role — reserved for scheme-specific use

A reserved character is only dangerous where it could be confused with the role it plays above. An & inside a query parameter value will be misread as the next parameter starting — it needs encoding there. The same & sitting in a path segment isn't reserved for anything a path parser looks for, so it can be left alone. There is no single yes/no answer per character — it depends on which part of the URL it's in.

What JavaScript's built-in encoders actually leave alone

In practice, most encoding happens through encodeURIComponent or encodeURI, and each has an exact, fixed safe set — worth knowing precisely rather than guessing:

FunctionLeaves unescapedUse for
encodeURIComponentA–Z a–z 0–9 - _ . ! ~ * ' ( )A single value — a query parameter, a path segment
encodeURIA–Z a–z 0–9 - _ . ! ~ * ' ( ) ; / ? : @ & = + $ , #A complete URL, where / ? : @ & = # etc. are doing real structural work

Note the mismatch with strict RFC 3986: both functions additionally leave ! ~ * ' ( ) unescaped even though the spec calls them reserved — a historical quirk from an earlier RFC 2396 definition of "unreserved" that the JavaScript spec never updated. They're harmless to leave unescaped in practice, but don't assume every URL-handling library treats them the same way — some encoders do escape them.

Quick answer by where the value sits

LocationMust encode
Path segment/ (or it splits into two segments), ?, #, space, and anything non-ASCII
Query parameter key or value&, =, #, + (if it should stay literal), space, and anything non-ASCII
FragmentSame as a query value — # can't appear again, space and non-ASCII still need it
Userinfo (user:pass@host):, @, and /, since any of those would be misread as ending the userinfo section early
HostnameNot percent-encoded at all — non-ASCII hostnames use punycode (xn--) instead, a completely different mechanism

Two characters worth calling out specifically

  • Space. Never valid literally, anywhere in a URL. %20 is always correct; a query string will also accept + for it specifically, following the older form-encoding convention — see URL Encoding Explained for why that exception exists.
  • Non-ASCII characters (accented letters, CJK text, emoji) always need encoding — there's no context where they're left literal — and each one expands into multiple %XX groups, one per UTF-8 byte, not one group per character.

When in doubt

If you're encoding one value that's going into a URL — not building the whole URL by hand — encodeURIComponent (or your language's equivalent) is almost always the right call, precisely because it encodes everything structural and leaves the rest alone. It's rarely wrong to encode a character that didn't strictly need it; it's often a real bug to leave one unencoded that did.

Try it yourself

Encode or decode a value directly with URL Encode and URL Decode. To check whether a URL you've already got is missing encoding, has it doubled up, or has a malformed % sequence, run it through URL Inspector. All run entirely in your browser.

Related tools