Percent encoding: choose the right operation for each URL component

Avoid double encoding, broken query parameters and plus-sign mistakes with component-level examples.

Percent encoding is useful only when you know which URL component you are constructing. Encoding a query value is different from serialising a complete URL, and decoding form data is different from replacing every plus sign in arbitrary text. Most encoding bugs come from losing that context.

Start with the raw value, choose the component it belongs to and let an appropriate API serialise it once. Keep encoded and decoded values clearly named. A string that happens to contain percent signs is not automatically ready for another decode.

Understand what a percent escape represents

A percent escape contains a percent sign followed by two hexadecimal digits representing a byte. Non-ASCII text is commonly encoded as UTF-8 bytes before those bytes are escaped. One visible character can therefore produce several escapes.

The URL component encoder uses encodeURIComponent and decodeURIComponent for its two operations. It handles a component's text, not the structure of an entire URL. Its decoder does not translate plus signs into spaces.

For example, the UTF-8 bytes of é are C3 A9, so an encoded component contains %C3%A9. This is different from encoding an arbitrary single byte E9 under another character encoding. Agree on the text encoding before comparing results across systems.

Raw value:      tea & coffee
Encoded value:  tea%20%26%20coffee

Raw value:      a+b
Encoded value:  a%2Bb

The ampersand and plus sign are data in these examples. Encoding prevents them from being confused with syntax used by a surrounding query or form representation. The complete URL still needs its own separators outside the encoded value.

Keep structure outside the encoded data

Suppose an application builds a search URL by concatenating ?q= with a raw user query. A query containing an ampersand can introduce another parameter instead of remaining part of q. Encoding the value, or using URLSearchParams, preserves the intended boundary.

Do not encode the entire completed URL with encodeURIComponent and expect it to remain navigable as the same structure. That operation escapes separators such as colon and slash because it treats the input as one component. It can be appropriate when a whole URL is itself a parameter value, but that is a different role.

For paths, decide whether the input is one segment or several segments. A slash inside a single segment is data that may require encoding; a slash separating intended segments is structure. Server routing and decoding rules must agree with the representation you choose.

Choose an operation by context
TaskUseful approachAvoid
Add a query valueURLSearchParams or component encodingConcatenating unescaped user text
Build a complete web URLURL object with explicit componentsEncoding all separators as data
Decode a percent-encoded componentOne defined decode stepRepeated decoding until percent signs disappear
Parse form-urlencoded dataA form-aware parserTreating plus exactly like generic URL text
Encode a path segmentA segment-aware contractGuessing whether embedded slashes are separators

Treat plus signs according to the format

In application/x-www-form-urlencoded processing, a plus sign represents a space. A literal plus sign must be represented appropriately so it survives parsing. Generic percent decoding through decodeURIComponent does not apply that form-specific plus-to-space step.

This distinction often breaks email addresses or search terms containing plus signs. If one system sends a raw plus and another applies form parsing, the resulting value can contain a space. Inspect the actual wire representation and Content-Type before changing the decoder.

URLSearchParams uses form-style serialisation for query parameters. That is convenient when both sides use the corresponding parsing rules. Do not pre-encode a value before passing it to set, because the API will encode the percent signs again as ordinary data.

const url = new URL('https://example.com/search');
url.searchParams.set('q', 'tea & coffee');
url.searchParams.set('label', 'a+b');
// Read back through searchParams to verify the original values.

Recognise double encoding

A literal percent sign becomes %25 when component-encoded. Therefore an already encoded slash, %2F, becomes %252F after another encoding step. If the receiver decodes once, it sees %2F rather than a slash. That is a strong clue that the sender encoded data twice.

Trace the value through each layer: form input, application state, URL builder, HTTP request and server parser. Name variables according to whether they hold raw text or a serialised component. Ambiguous names such as safeValue can hide an extra transformation.

Fix the ownership of encoding rather than adding compensating decodes downstream. If every layer tries to repair the previous layer, unusual inputs can produce inconsistent routing or validation. One component should own serialisation for each boundary.

Repair a filename parameter that changes on download

Suppose a filename is report + notes.txt. The frontend manually replaces spaces with plus signs and then passes the result to URLSearchParams. The server receives a value that no longer clearly distinguishes original plus signs from substituted spaces.

Keep the raw filename in application state and pass it directly to the query builder. On the server, parse the query once with the intended parser. Compare the decoded value with the original in a round-trip test containing spaces, plus, ampersand, percent and non-ASCII text.

Do not use the decoded filename directly as a filesystem path. URL decoding and path safety are separate concerns. Apply the application's filename and storage rules after parsing, without allowing directory traversal or unintended path separators.

Verify the final response's download naming separately. A query parameter can be correct while Content-Disposition encoding is wrong. Each protocol boundary has its own syntax, so fixing URL encoding does not automatically fix every later header representation.

Reject malformed escapes visibly

A percent sign without two valid hexadecimal digits is malformed for a percent escape. Invalid UTF-8 sequences can also make a text decode fail. The component decoder reports these failures instead of silently inventing replacement text.

Preserve the original input when displaying an error. A user may have pasted an already-decoded string containing a literal percent sign. Clarify whether the operation expects raw text to encode or encoded text to decode, rather than treating every percent sign as proof of corruption.

For binary data, use a byte-oriented tool and a defined protocol rather than assuming every escaped byte sequence is valid text. decodeURIComponent is a text operation with Unicode expectations. An arbitrary byte buffer needs a different handling contract.

Canonicalise consistently before security decisions

If a proxy, router and application decode differently, they can disagree about which resource a request names. Define where decoding occurs and what representation authorisation checks use. Test encoded delimiters and repeated escapes through the actual deployment path.

A validation rule that checks raw text for a forbidden slash can miss an encoded slash if a later layer decodes it. Conversely, decoding too early can create separators that were intended as data. The fix is a consistent parsing contract, not a growing list of string replacements.

Keep logs clear about representation. Store or label the raw request target and parsed values appropriately, with sensitive fields redacted. Otherwise a support engineer may compare encoded text from one log with decoded text from another and infer a change that never occurred.

Check repeated parameter names explicitly

A query can contain the same parameter more than once. Some parsers return the first value, others the last, and others a list. Encoding each value correctly does not settle that application contract. Use URLSearchParams.getAll when inspecting multiplicity, then define what the server accepts. Test duplicate names at security-sensitive boundaries so a proxy and application cannot authorise different values from the same request.

Frequently asked questions

Is URL encoding encryption?

No. It changes representation so data fits a component's syntax. Anyone can decode it without a secret. Do not place passwords or access tokens in URLs merely because they are percent-encoded; logs, history and shared links can still expose them.

Why does the tool leave plus signs unchanged when decoding?

It performs generic component decoding through decodeURIComponent. Plus-to-space conversion belongs to form-urlencoded parsing. Choose the parser that matches the source format instead of assuming every URL-related decoder uses the same rule.

Should I encode before calling URLSearchParams.set?

Normally pass the raw value and let URLSearchParams serialise it. Pre-encoding can cause percent signs to be encoded again, producing double encoding. Verify by reading the value back through the same parameter API and comparing it with the original text.

Why does one character become several escapes?

Non-ASCII characters can use multiple UTF-8 bytes. Each escaped byte receives its own percent sequence. Count bytes and code points separately when debugging; visible character count does not predict the length of an encoded component.

Can I safely decode a complete URL?

Parse it into components and decode only according to their roles. Blind decoding can turn encoded data into structural delimiters. Preserve the original URL and use a standards-based parser for destination decisions.

What does %252F usually indicate?

It represents an encoded percent sign followed by 2F, so one decode yields %2F. It may reflect intentional nesting or accidental double encoding. Trace the boundary contract before deciding whether a second decode is appropriate.

What belongs in a round-trip test?

Include spaces, literal plus, ampersand, equals, percent, slash, non-ASCII text and empty values. Test repeated parameter names if the application supports them. Compare parsed values and parameter structure, not only the visual appearance of the final URL.

Sources and further reading

50 useful tools · ↑ ↓ to navigate · Enter to open · Esc to close