Why a URL cannot hold arbitrary text
A URL is not a string, it is a structure. Slashes separate path segments, a question mark starts the query, an ampersand separates one parameter from the next, an equals sign divides a name from its value, and a hash begins the fragment. Every one of those characters is doing a job.
Put a value containing one of them into a URL and the structure changes meaning. A search for fish & chips becomes a parameter q=fish followed by a second parameter called chips, which the server has never heard of. A product code containing a hash truncates the address at that point, because everything after a hash is a fragment the browser never sends to the server at all.
Percent-encoding is the escape hatch. Each problematic character becomes a percent sign and two hexadecimal digits — a space becomes %20, an ampersand %26 — so the value can sit inside the URL without being read as punctuation.
Which characters need it
Three groups, and the middle one is where the judgement lies.
- Unreserved: letters, digits, and
- . _ ~. Safe everywhere, never need encoding, and encoding them anyway is legal but pointless.
- Reserved:
: / ? # [ ] @ ! $ & ' ( ) * + , ; =. These have structural meaning. Whether they need encoding depends entirely on whether you mean them structurally.
- Everything else: spaces, accented letters, non-Latin scripts, punctuation outside the two lists. Always encode.
That middle group is the whole difficulty. A slash separating path segments must stay a slash. A slash inside a product name must become %2F. The character is identical; the intent is not, and no tool can infer it for you.
Encode the value, never the address
This is the mistake to internalise, because it produces a link that looks encoded and works nowhere.
The [msm_tool slug="url-encoder"] encodes for a single component, which means the reserved characters are escaped too. That is exactly right for one parameter value and exactly wrong for a whole address: run a complete URL through it and its own colons, slashes, question mark and ampersands all become percent sequences. The result is not a URL any more, it is a string that used to be one.
So the workflow is: take the value you are inserting, encode that, and paste the result into an address you assemble yourself. Build https://example.com/search?q= by hand and append the encoded value. The structure stays readable and only the payload is escaped.
Encode exactly once
Running an encoded string through again encodes the percent signs themselves. %20 becomes %2520, and the link fails in a way that looks like a typo rather than a bug.
It happens most often when two layers of code each try to be helpful — a template escapes a value, then a redirect handler escapes the whole URL again. The symptom is a visible percent code in an address bar, or a search result page reporting no matches for something containing %20. If you see a percent sequence surviving a decode, you have found double encoding.
Spaces, and the plus sign
A space can be written as %20 or, in one specific context, as a plus. The plus form belongs to application/x-www-form-urlencoded, the format an HTML form posts in, and means a space only there.
Everywhere else a plus is a literal plus. In a path, in a fragment, in a value that will be read by something not expecting form encoding, a plus that was meant to be a space is simply a plus. %20 is valid in every part of a URL, which makes it the safer output and the one to prefer unless you are specifically building a form-encoded body.
Non-Latin text
Characters outside ASCII are converted to their UTF-8 bytes, and each byte becomes its own percent sequence. A single accented letter usually produces two; a character in an Arabic, Urdu or CJK script often produces three. A short phrase can expand into a long and unreadable string, and that is correct rather than a fault.
Domain names work differently. A non-Latin domain is not percent-encoded but converted to Punycode, an xn-- prefixed form that lives in DNS. Only the path, query and fragment use percent-encoding.
What encoding is not
It is not a security measure and it is not a substitute for escaping elsewhere. Encoding makes a value safe to travel inside a URL. It does nothing about what happens when that value arrives.
A value going into HTML needs HTML escaping; a value going into a database query needs a parameterised query; a value going into a shell command needs shell quoting. These are separate mechanisms for separate contexts, and a string can legitimately need two of them applied in the right order. Decoding a URL parameter and writing it straight into a page is a cross-site scripting vulnerability regardless of how carefully it was encoded on the way in.
Encoding is a transport concern. Treat it as one, and escape again for whatever the value is being handed to next.