flashman
← All guides

Encode one URL component, not the whole URL

Use encodeURIComponent on a single URL piece, keep reserved punctuation outside it, and treat a malformed percent escape as a broken input.

2026-10-18 · 8 min read

  • url
  • encoding
  • http

A URL is a sequence of components separated by reserved characters. encodeURIComponent escapes those reserved characters because it is preparing one component to sit inside a URL. encodeURI leaves them alone because it is preparing a whole URL. Flashman's URL tool calls encodeURIComponent, or decodeURIComponent, on everything in the box. It never calls encodeURI.

Encoding https://flashman.dev/?q=hello world therefore prints https%3A%2F%2Fflashman.dev%2F%3Fq%3Dhello%20world. That is the correct result for a component, and it is not a link. If a redirect or an analytics tag stored that string as the location, the bug is which function was chosen, not a flaky encoder.

Build the URL after the values are encoded

Encode hello world to hello%20world and place it after ?q=. Leave the ?, the =, and the scheme outside the box. a+b encodes to a%2Bb. A slash and a question mark inside a value, as in a b/c?, encode to a%20b%2Fc%3F. café encodes to caf%C3%A9, the UTF-8 bytes of the character. 100% encodes to 100%25.

Unescaped characters are letters, digits, hyphen, underscore, dot, exclamation mark, tilde, asterisk, single quote, and parentheses. A star or a tilde that survives is expected. A colon that survives means this tool was not the function that ran.

  • Encode a path segment on its own.
  • Encode a query value on its own.
  • Assemble the URL with literal reserved characters.
  • Do not encode a string that already contains % escapes unless you mean to escape the percent sign.

Plus signs and double encoding are different bugs

This decoder does not treat + as a space. hello+world decodes to hello+world. A body produced as application/x-www-form-urlencoded, where + means space, needs a form decoder. Running it through this box will leave the plus in place and will still decode any %20 you also had.

already%20encoded encodes to already%2520encoded. One decode returns already%20encoded. A second decode returns the space. %2520 in a log is two encodings. %20 is one. Diff those two strings before you add another decode at the boundary. Removing a decode that a signature covered will break the signature even when the page starts loading.

A bad percent escape throws

%, %2, and %zz throw URI malformed. The tool shows that exception with an Error: prefix. It does not return a partial string and it does not skip the bad byte. A value cut off by a log truncator, or a % that was inserted as a literal and never encoded to %25, fails the whole decode.

Fix the producer so a literal percent is encoded, or so the full two hex digits are preserved. Do not catch the error and pass the original string on as if it were decoded. Callers cannot tell a successful decode of a value that happened to contain a percent sequence from a failure you swallowed.

Record the component beside the final URL

Store the raw value, the encoded component, and the assembled URL. Diff the encoded component with the query field the server parsed. If the server saw %253A, a full URL was encoded and then encoded again. If it saw a plus for a space, a form encoder was in the path.

Use the URL tool on one component, the JSON formatter when that component is a JSON string, and diff on the encoded forms. The reserved characters belong in the assembly step, written as themselves, where a reviewer can see them.

Try these tools