Blog/Security
SecurityMarch 12, 2026 · 7 min read

encodeURIComponent vs encodeURI — Stop Guessing Which One to Use

You've got a search API. A user types hello & goodbye = forever. You blindly interpolate that into a query string, ship it, and six months later someone files a bug because & splits the parameter in half and the backend gets two garbage keys instead of one. You've seen this. Everyone has seen this.

The fix is one function call. But which one?

What Each Function Actually Encodes

The difference comes down to which characters get percent-encoded.

encodeURI leaves these characters alone because they're structurally meaningful in a URL:

; , / ? : @ & = + $ # A-Z a-z 0-9 - _ . ! ~ * ' ( )

encodeURIComponent encodes everything except unreserved characters:

A-Z a-z 0-9 - _ . ! ~ * ' ( )

That means encodeURIComponent will encode : / ? # @ & = + , $ — all the characters that give a URL its shape.

Live comparison — type anything or try a sample

Input string
encodeURI()keeps URL structure chars intact
hello%20&%20goodbye%20=%20forever
encodeURIComponent()encodes everything including structure
hello%20%26%20goodbye%20%3D%20forever

✓ For query params and user input: always use encodeURIComponent

Use encodeURI only for complete, already-assembled URLs with no user data

Concrete example:

const value = "hello & goodbye=forever";

encodeURI(value);
// "hello%20&%20goodbye=forever"
// ❌ & and = survive — they'll break your query string

encodeURIComponent(value);
// "hello%20%26%20goodbye%3Dforever"
// ✅ & → %26, = → %3D — safe to embed as a query param value

The Production Bug encodeURI Creates

Say you're building a redirect URL with a returnTo parameter:

/login?returnTo=https://app.example.com/dashboard?tab=settings&user=42

If you use encodeURI on the returnTo value, the ?, =, and & in the value are left unencoded. Your server now sees:

returnTo = https://app.example.com/dashboard
tab = settings
user = 42

Three parameters instead of one. The login redirect breaks, and in a worst case where you're using that returnTo value without validation, you've got an open redirect vulnerability.

encodeURIComponent would have produced:

/login?returnTo=https%3A%2F%2Fapp.example.com%2Fdashboard%3Ftab%3Dsettings%26user%3D42

One parameter. Correct.

When encodeURI Is Actually the Right Call

Almost never — but there is a valid use case: you've received a URL string that might contain non-ASCII characters and you need to normalize it without destroying its structure.

const messyURL = "https://example.com/résumé?lang=fr";
encodeURI(messyURL);
// "https://example.com/r%C3%A9sum%C3%A9?lang=fr"

The path gets fixed, the ? and = stay intact. This is a narrow, specific use case — not the default.

The Gotcha Junior Devs Miss: + vs %20

encodeURIComponent encodes spaces as %20. Some server-side parsers (especially older PHP and Java URLDecoder) expect + for spaces in query strings, not %20. They're both valid in different contexts:

  • %20 — RFC 3986 (URI spec), correct for path segments and modern query parsers
  • + — HTML form encoding (application/x-www-form-urlencoded), used by URLEncoder.encode() in Java and urlencode() in PHP

If you're building a query string to submit as a form, + for spaces is fine. If you're constructing a URL programmatically to pass to a fetch/HTTP client, use %20 via encodeURIComponent.

Mixing them up causes intermittent decoding bugs that only appear with user input containing spaces — the kind that pass your own tests but blow up in production.

Building Full URLs Safely

Don't manually concatenate URLs. Use the URL constructor or your language's URI builder:

// ✅ Correct — let the URL API handle encoding
const base = new URL("https://api.example.com/search");
base.searchParams.set("q", "hello & goodbye=forever");
base.searchParams.set("returnTo", "https://app.example.com/dashboard?tab=1");

console.log(base.toString());
// https://api.example.com/search?q=hello+%26+goodbye%3Dforever&returnTo=https%3A%2F%2Fapp.example.com%2Fdashboard%3Ftab%3D1

// ❌ Wrong — manual concatenation with wrong encoder
const url = `https://api.example.com/search?q=${encodeURI(query)}`;

URLSearchParams, URLComponents, URIBuilder, http_build_query — they all call the equivalent of encodeURIComponent under the hood. Let them do the work.

Use encodeURIComponent (or your language's equivalent) any time you're embedding a value inside a URL — query params, path segments, fragment identifiers. Use encodeURI only when you have a fully-formed URL that needs non-ASCII normalization, which is rare.

The URL API in every modern language handles encoding correctly when you build URLs through it rather than string interpolation. That's the real answer: stop manually constructing URLs, and the encodeURI vs encodeURIComponent question mostly goes away.

Share this post

Frequently Asked Questions

Related posts

Prompt Injection: Why You Can't Fix It With a Better Prompt

Prompt injection isn't a filtering problem you can prompt your way out of. It's an architecture problem — the model can't tell your instructions from the data it's reading. Here's what actually contains it.

Jul 28, 2026 · 11 min read

Phishing URLs: How Attackers Fake Domains (and How to Read Them)

Typosquatting, homograph attacks, subdomain deception, and the @ trick — learn the handful of URL tricks behind most phishing, and the one habit that beats all of them.

Jun 13, 2026 · 7 min read

What Is CSV Injection — and How to Stop It in Your Export Feature

CSV (formula) injection turns a harmless 'export to spreadsheet' button into code execution on whoever opens the file. Here's how the attack works and how to prevent it.

Jun 13, 2026 · 7 min read