Skip to content
← escentmachine field notes

A query value needs a boundary, not another escape pass

A string containing an ampersand can be either a query value or part of a URL’s structure. No encoder can infer which meaning you intended. Choose the boundary before encoding, then keep that boundary consistent when decoding.

For one value, encode the component

Suppose a search phrase is café & tea. It belongs inside a single query parameter. Component encoding protects the ampersand and spaces so they cannot become a second parameter or invalid URL whitespace.

Input: café & tea
Operation: Encode
Boundary: Component

Expected: caf%C3%A9%20%26%20tea

Run the query-value example →

Whole URL encoding preserves structural characters such as /, ?, &, = and #. Encoding the complete string https://api.example.test/search?q=café & tea therefore leaves the ampersand as a separator. It cannot know that this particular ampersand was supposed to be part of the phrase.

Whole URL input:
https://api.example.test/search?q=café & tea

Expected encodeURI output:
https://api.example.test/search?q=caf%C3%A9%20&%20tea

A plus sign has context

In form-style query parsing, + represents a space. The literal plus must be encoded as %2B. In standalone component decoding, decodeURIComponent does not turn + into a space. The URL Workbench follows these distinct rules rather than applying one decoder everywhere.

Query: ?q=a+b&q=a%2Bb

Expected ordered parameters:
1. q = a b
2. q = a+b

Repeated keys remain separate entries in source order. Converting query parameters into a plain object would lose this information, so the Workbench returns an ordered list of key/value pairs. An empty value is different from a missing key; both are visible in the parser output.

Decode once, at the right boundary

Encoding an existing percent sign produces %25. Decoding twice can turn data into syntax unexpectedly. Track whether a value is raw text or already encoded rather than adding encode/decode calls until an output looks plausible. In whole-URL decoding, reserved separators such as %26 stay escaped; component decoding turns %26 into an ampersand.

decodeURIComponent("%E2%28%A1")

Expected: malformed UTF-8 error; input preserved.

Parsing is local and never visits the URL. The HTTP(S) parser uses browser URL normalization, which may canonicalize a host or remove a default port. Path and fragment fields remain encoded so their boundaries stay visible. Use the encoding operation separately when you intend to transform a component.

Previous note
Next note