DEV Community

Dmytro
Dmytro

Posted on

5 URL Encoding Bugs That Silently Break Your App

Every web developer hits URL encoding bugs eventually. A redirect loop that only happens with certain usernames. A search feature that breaks when someone types C++. An API that returns garbage when the query contains emoji.

These bugs are annoying because they fail silently — the URL looks fine in the browser, but the server receives corrupted data.

Here are 5 encoding mistakes I've seen (and made) in production, and how to fix each one.

1. Using encodeURI when you need encodeURIComponent

This is the classic one. You're building a URL with user input:

// User searches for "node.js & deno"
const query = 'node.js & deno';

// WRONG — encodeURI preserves & and =
const bad = `https://api.example.com/search?q=${encodeURI(query)}`;
// "https://api.example.com/search?q=node.js%20&%20deno"
// Server sees: q = "node.js " and a second param " deno" = undefined

// RIGHT — encodeURIComponent encodes everything
const good = `https://api.example.com/search?q=${encodeURIComponent(query)}`;
// "https://api.example.com/search?q=node.js%20%26%20deno"
Enter fullscreen mode Exit fullscreen mode

encodeURI() is designed for complete URLs — it preserves :, /, ?, &, =, #. If you use it on a value that contains &, the server interprets it as a parameter separator.

Rule: encodeURIComponent() for values. encodeURI() basically never (use the URL API instead).

2. Double encoding

This one is insidious. You encode a value, store it, then encode it again when building the URL:

const filename = 'my report.pdf';
const encoded = encodeURIComponent(filename); // "my%20report.pdf"

// Later, somewhere else in the codebase...
const url = `https://cdn.example.com/files/${encodeURIComponent(encoded)}`;
// "https://cdn.example.com/files/my%2520report.pdf"
//                                    ^^^^ %25 is the encoded %
Enter fullscreen mode Exit fullscreen mode

The % in %20 gets encoded to %25, producing %2520. The server decodes it to %20 (a literal string), not a space.

Fix: Encode raw values exactly once. If you're not sure whether a string is already encoded, decode first:

const safeEncode = (str) => encodeURIComponent(decodeURIComponent(str));
Enter fullscreen mode Exit fullscreen mode

3. Forgetting that + and %20 are different

Two ways to encode a space. Two different standards.

Encoding Standard Where it works
%20 RFC 3986 (URI) Everywhere
+ HTML forms (application/x-www-form-urlencoded) Query strings only
// These produce different output
encodeURIComponent('hello world')  // "hello%20world"
new URLSearchParams({q: 'hello world'}).toString()  // "q=hello+world"
Enter fullscreen mode Exit fullscreen mode

Both are valid in query strings. But + in a path segment means a literal plus sign, not a space. I've seen this break file downloads where filenames contained spaces — the path used +, and the server treated it as a literal +.

Fix: Use %20 when in doubt. It works everywhere.

4. Not encoding path segments

// User uploads a file called "Q1 Report (Final).pdf"
const filename = 'Q1 Report (Final).pdf';

// WRONG
const url = `https://cdn.example.com/files/${filename}`;
// Spaces and parentheses in the path = broken

// RIGHT
const url = `https://cdn.example.com/files/${encodeURIComponent(filename)}`;
// "https://cdn.example.com/files/Q1%20Report%20(Final).pdf"
Enter fullscreen mode Exit fullscreen mode

Most developers remember to encode query parameters but forget that path segments need encoding too. Any user-generated filename, category name, or slug with special characters will break without encoding.

5. Encoding the entire URL with encodeURIComponent

The opposite extreme — encoding everything:

const url = 'https://example.com/search?q=hello';

// Don't do this
encodeURIComponent(url)
// "https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dhello"
// This is not a valid URL anymore
Enter fullscreen mode Exit fullscreen mode

This breaks the URL structure by encoding ://, /, ?, and =. I've seen this in redirect handlers where the full URL was passed through encodeURIComponent "for safety."

Fix: If you need to pass a URL as a parameter value, encode the whole URL with encodeURIComponent. If you need to navigate to a URL, don't encode its structural characters — use the URL API:

const url = new URL('https://example.com/search');
url.searchParams.set('q', 'hello world & more');
url.searchParams.set('redirect', 'https://other.com/page?id=1');
url.toString()
// Everything is encoded correctly, automatically
Enter fullscreen mode Exit fullscreen mode

The modern solution: just use the URL API

Honestly, most of these bugs disappear if you stop string-concatenating URLs:

// Instead of manual encoding...
const url = new URL('https://api.example.com/search');
url.searchParams.set('q', userInput);
url.searchParams.set('page', '1');
url.searchParams.set('callback', 'https://mysite.com/done?status=ok');

fetch(url)  // All encoding handled automatically
Enter fullscreen mode Exit fullscreen mode

The URL and URLSearchParams APIs handle encoding correctly in every case. They're available in all modern browsers and Node.js.


If you want to quickly test how different strings get encoded (or decode a messy URL from server logs), I built a free URL Encoder/Decoder that runs entirely in your browser — nothing gets sent to a server.

For a complete reference of all percent-encoded characters, there's also a URL Encoding Cheat Sheet you can bookmark.

What encoding bugs have bitten you? I'd love to hear your war stories in the comments.

Top comments (0)