Guide
How to detect and fix a double-encoded URL
Recognize patterns such as %2520, find the boundary that encoded twice, and remove one layer without activating hidden delimiters.
by Tools in a Tab · Published on · Reviewed on
Short answer
%2520, %252F, or %253F often signals double encoding: %25 represents
the percent sign, so decoding one layer leaves %20, %2F, or %3F. Treat
this as evidence to investigate, not permission to decode twice. Identify the
component that needed encoding and fix the boundary that added the extra layer.
How %2520 is produced
Component encoding turns a space into %20. If another layer receives those
three literal characters and encodes them again, % becomes %25, producing
%2520:
space → %20 → %2520
The URL encoder warns when input being encoded
already contains a %HH escape or when decoding one layer still leaves
escape-like text. The warning is not proof of an error: sometimes an
application intentionally transports the literal string %20.
Useful signals and false positives
Look for %25 followed by two hexadecimal digits, especially %2520, %252F,
%253A, %253F, %2526, and %253D. Compare the value at every boundary:
browser, proxy, framework, logs, and application. The first point where %20
becomes %2520 identifies the duplicated encoding step.
Not every %25 is wrong. The human value 50% is correctly represented as
50%25, and a page teaching the literal text %20 must transport it as
%2520. Only the field contract distinguishes literal data from an escape
that was applied twice.
Decode a component, not the whole address
Suppose a query parameter contains a return URL. The outer URL encodes the inner one as a value, so nested escapes are expected:
/login?next=https%3A%2F%2Fexample.test%2Fa%3Fx%3D1%25202
Parse the outer URL and extract next first, then decode exactly the layer
defined for that value. Applying decodeURIComponent twice to the complete
address can turn %26 into & too early, creating another parameter or
changing path structure.
RFC 3986 recommends encoding or decoding once unless a specific scheme defines additional layers.
Fixing the source
Keep raw values inside the program. If a function returns an already encoded
component, do not pass it through encodeURIComponent again. Better, change
the contract so it returns the raw value and make one layer responsible for the
URL boundary.
For queries, call URLSearchParams.set(name, rawValue). For path segments,
encode each data value once immediately before inserting it. Avoid storing
pre-encoded values in a database because different consumers may need HTML,
JSON, URL, or another output context.
Safe repair procedure
- Preserve the exact input and final URL so the failure is reproducible.
- Parse scheme, path, query, and fragment instead of editing the whole string.
- Identify data fields and the number of layers documented by the protocol.
- Decode one layer of the affected component.
- If
%HHremains, determine whether it is literal text or another real layer. - Correct the producer and add a round-trip test.
Never add a global rule that replaces %25 with %. It may introduce invalid
escapes, change delimiters, or let user-controlled input alter URL structure.
Tests worth keeping
Cover a space, literal percent sign, &, =, /, Unicode, and a nested URL.
Assert both the received value and the final structure. A strong test does not
merely check that %2520 disappeared; it proves that the original data returns
after the exact documented number of layers.