Percent-Encoding — Path vs Query vs Fragment

(Updated: July 16, 2026 ) url-encode webdev HTTP

A support ticket lands with a “broken share link.” Someone pasted a full URL into a helper that ran encodeURIComponent on everything. The result starts with https%3A%2F%2F… and nowhere is that a valid navigation target. Percent-encoding is not one global switch — it is a set of rules that change by which part of the URL you are writing.

Three contexts, three rules

PartTypical characters that must be encodedSafe to leave alone
Path segmentspace, ?, #, / (if it is data, not a separator)unreserved letters/digits -._~
Query valuespace, &, =, #, non-ASCIIstructure of ?key=value&… itself
Fragmentspace and reserved chars the fragment parser cares about# that starts the fragment

Encoding the structure of a URL (scheme, ://, path slashes that mean hierarchy, ? that starts the query) is how you destroy it. Encoding the payload inside a segment or a value is how you keep it intact.

encodeURI vs encodeURIComponent

In browsers and Node:

  • encodeURI — meant for a full URL string. It leaves : / ? # and similar structural characters alone. Useful when you already have a mostly-valid URL and only need to fix unsafe bytes.
  • encodeURIComponent — meant for a single component. It encodes almost everything reserved, including / and ?. Correct for a search term, a filename in a path segment, or a query parameter value.

Rule of thumb: build URLs from pieces. Encode each piece with encodeURIComponent, then join with literal ?, &, =, and /.

const base = "https://api.example.com/v1/search";
const q = encodeURIComponent("c++ & rust");
const url = `${base}?q=${q}`;
// https://api.example.com/v1/search?q=c%2B%2B%20%26%20rust

Path traps people hit in production

Filenames with spaces/files/My Report.pdf should become /files/My%20Report.pdf. Leaving the space works in some browsers and fails in others or in CDNs.

Slashes inside an ID — If the ID is a/b, that is two path segments unless you encode: /items/a%2Fb. Frameworks that decode once and re-split on / still surprise teams; know whether your router decodes before matching.

Unicode — Usernames and product slugs with non-ASCII should be UTF-8 then percent-encoded. Copy-paste from a spreadsheet often introduces “smart” punctuation that looks fine in the UI and 404s on the server.

Query string specifics

& and = separate pairs. If a value contains them, encode the value, never the whole query string as one blob after the fact.

# Wrong mental model
encodeURIComponent("?q=a&b=1")  // encodes the ? and & — now one useless parameter

# Right
"?q=" + encodeURIComponent("a&b=1")

Some legacy stacks still emit + for spaces in queries. When debugging redirects, decode and compare the logical string, not the wire form.

Fragments and client-only state

Anything after # is not sent to the server on a normal navigation. Encoding still matters for in-page routers and OAuth-style flows that stash state in the hash. Do not assume the server “saw” a broken fragment — it never arrived.

Double-encoding: how to unwind it

Symptoms: logs show %252F or %253A. Something encoded an already-encoded string.

  1. Decode once; if the result still looks encoded, decode again until it stabilizes or looks like plain text.
  2. Decide the target context (path vs query).
  3. Encode once for that context.
  4. Persist the canonical form so the next hop does not encode again.

Use url-encode when you need a quick encode/decode pass on a suspicious string without shipping it to a remote service — paste the path segment or query value alone, not the whole URL, unless you intentionally chose encodeURI-style behavior.

  1. Never run encodeURIComponent on a full absolute URL.
  2. Encode path segments and query values independently.
  3. Prefer %20 for spaces unless you are speaking form-urlencoded to a known peer.
  4. Log both raw and decoded forms when investigating 404s on “weird” URLs.
  5. Treat % in user input as data — encode it to %25 so a later decode does not invent new sequences.

Percent-encoding is boring until one wrong helper turns every share link into percent soup. Keep structure literal, encode payloads, and decode before you re-encode when cleaning legacy data.