iKit
Technical · 9 min read ·

encodeURIComponent vs encodeURI: When to Use Which (2026)

encodeURIComponent vs encodeURI trips up every JavaScript dev once — here's the actual rule, the characters each protects, and when to pick which in 2026.

encodeURIComponent vs encodeURI: When to Use Which (2026)

encodeURIComponent vs encodeURI — Which JavaScript Helper to Use, and When

You paste a URL into fetch(), it 404s, and the only thing different from the working version is one percent-sign. Welcome to the encodeURIComponent vs encodeURI debate — the JavaScript API pair that fewer than half of working developers can describe accurately on the first try. This post fixes that. By the end you'll know exactly which one protects what, why the smaller function is almost always the right pick, and the three places using the wrong one silently breaks a request.

TL;DR

  • encodeURIComponent percent-encodes every reserved URL character — use it for query values and path segments.
  • encodeURI leaves /, ?, #, &, = alone — use it only when encoding a whole URL.
  • Nine times out of ten, encodeURIComponent is the right answer.
  • A ? inside a value will silently cut your URL in half if you used encodeURI.
  • For complex query strings, URLSearchParams beats hand-rolling either function.

What encodeURI and encodeURIComponent Actually Do

Both functions percent-encode bytes that aren't safe in a URL. Both follow RFC 3986 for which bytes count as "unreserved" and stay literal. The difference is which reserved characters they treat as part of URL syntax and leave alone, versus which they treat as content that must be escaped. That's the whole story — but the consequence is that they are not interchangeable, and using the wrong one is one of the easier ways to ship a broken request.

encodeURI: the "leave the URL alone" version

encodeURI is designed for taking a string that is already a complete URL — scheme, host, path, query, fragment — and escaping the characters that absolutely cannot survive in a URL. It does not touch characters that have structural meaning, because doing so would change which URL you're actually pointing at. The characters it skips are these:

// encodeURI does NOT encode any of these:
// A-Z a-z 0-9 - _ . ! ~ * ' ( )
// ; , / ? : @ & = + $ #

That second row is the important one. Forward slashes stay as slashes (so the path keeps its shape), question marks stay (so the query separator survives), and ampersands stay (so query parameters remain delimited). The function's only real job is making sure characters like spaces, accented letters, and non-Latin scripts get percent-encoded.

encodeURIComponent: the "encode every reserved character" version

encodeURIComponent assumes the opposite: it assumes you have a piece of URL — one value, one segment, one fragment — and that any reserved character inside that piece is content the user typed, not URL syntax. So it percent-encodes a much shorter "safe list":

// encodeURIComponent does NOT encode any of these:
// A-Z a-z 0-9 - _ . ! ~ * ' ( )
// (everything else gets percent-encoded)

That means /, ?, &, =, +, #, :, @ all turn into %2F, %3F, %26, %3D, %2B, %23, %3A, %40. If the piece you're encoding might contain any of those, this is the function you want.

Side-by-side: the characters each one leaves alone

Character encodeURI encodeURIComponent
/ ? # : @ left alone percent-encoded
& = + $ , left alone percent-encoded
; left alone percent-encoded
accented + non-Latin encoded encoded
space, <, >, " encoded encoded

The first three rows are the difference. The bottom two rows are identical. If your input never contains any character in the first three rows, the two functions return the same string — which is why so many bugs go undetected on the happy path and explode the first time a customer's name has an & in it.

When to Use Which (the 30-Second Rule)

Forget memorising tables. The rule is one sentence: use encodeURIComponent for anything you are substituting into a URL, and use encodeURI only for a URL string that is already 100% structurally correct and just needs its non-ASCII bytes cleaned up. In practice, almost every line of JavaScript that calls encodeURI would be better off calling encodeURIComponent on the variable parts and concatenating those into a hand-typed URL skeleton.

Building a URL from scratch

You're calling an API. The endpoint is https://api.example.com/search. The user typed a query. Don't call encodeURI on the whole thing — encode the variable:

const q = "Café & Bar 2026";
const url =
  "https://api.example.com/search?q=" +
  encodeURIComponent(q);
// .../search?q=Caf%C3%A9%20%26%20Bar%202026

The & inside the user's query becomes %26, which is exactly what you want — otherwise the server sees a second query parameter that does not exist.

Inserting a value into a query string

If you have three values and you're hand-rolling the query string, encode each value separately and use literal & and = between them. Better still, hand the job to URLSearchParams, which calls the right encoder for you internally:

const params = new URLSearchParams({
  q: "Café & Bar",
  page: 2,
  sort: "created_at:desc",
});
fetch(`/search?${params}`);
// /search?q=Caf%C3%A9+%26+Bar&page=2&sort=...

Note that URLSearchParams encodes spaces as +, not %20. That's because it follows the application/x-www-form-urlencoded rules, which is the format query strings use in practice. Both + and %20 decode back to a space on every standards-compliant server.

Encoding a path segment that contains slashes

A path segment containing a / is a classic trap. If a user's order ID has a slash in it — and they often do, in legacy systems — the URL has to encode that slash, or the server treats it as a path separator and routes the request to a different endpoint:

const orderId = "INV/2026/05/13";
const url =
  "/orders/" + encodeURIComponent(orderId);
// /orders/INV%2F2026%2F05%2F13

encodeURI would have left the slashes alone — and the request would have hit /orders/INV/2026/05/13, which is four path segments, not one.

Real-World Bugs From Picking the Wrong One

When the rules above are violated, the symptoms tend to be subtle: the request almost works. The wrong record updates. The search returns nothing. The webhook signature mismatches. Three patterns cover the majority of production bug reports we hear about.

"+" in a search box becomes a space

If a user types C++ tutorial and you encode the value with encodeURI, the + characters stay literal. On the receiving server, the URL parser interprets each + as a space (per the form-encoded rule), and the search engine looks up C tutorial. The fix is to encode the value with encodeURIComponent, which converts each + into %2B. This same +-versus-space confusion is the subject of an entire blog post on its own, because it has bitten so many teams.

A path segment with ? cuts your URL in half

Imagine an HR app where a department name happens to include a question mark — "Sales?" for an internal naming convention. Encoded with encodeURI, the URL becomes /department/Sales?/employees, and the ? immediately ends the path. Everything after is now interpreted as the query string. The fix is encodeURIComponent, which turns the ? into %3F and keeps it inside the path segment where it belongs.

Re-encoding an already-encoded string

The other direction is also a pitfall: calling encodeURIComponent on a string that's already been encoded turns %26 into %2526. When the server decodes the URL once, it gets %26 back as a literal — not an &. To avoid this, encode at exactly one layer: the moment a user-supplied value enters the URL. If you're not sure whether a string is already encoded, check for %[0-9A-Fa-f]{2} patterns first, or use decodeURIComponent defensively and accept a clean round-trip.

What These Functions Do NOT Cover

encodeURI and encodeURIComponent are useful but partial. They don't know about hash routing, they don't know about internationalised domains, and they don't follow the application/x-www-form-urlencoded rules perfectly. There are at least three cases where you need a different tool entirely.

RFC 3986 sub-delims and the apostrophe gap

encodeURIComponent leaves !, ', (, ), and * unencoded, even though RFC 3986 marks them as "sub-delims" — characters that could be reserved depending on context. Most servers handle these fine, but some signing schemes (notably OAuth 1.0a and AWS Signature V4) require them encoded. The standard fix is a wrapper:

function strictEncode(s) {
  return encodeURIComponent(s)
    .replace(/[!'()*]/g, c =>
      "%" + c.charCodeAt(0)
        .toString(16).toUpperCase()
    );
}

For inspecting whether a given byte was supposed to be encoded, the iKit URL Encoder shows the percent-encoded form side-by-side with the original.

Application/x-www-form-urlencoded uses "+", not "%20"

When you POST a form body with Content-Type: application/x-www-form-urlencoded, spaces become +, not %20. encodeURIComponent encodes spaces as %20, which still works on a sensible server but doesn't match the canonical format. URLSearchParams.toString() does it the form-encoded way. The MDN reference on URLSearchParams is worth a bookmark.

Internationalised domain names need Punycode, not percent-encoding

Neither function knows how to encode a hostname. If your URL is https://例え.jp/, applying encodeURI produces https://%E4%BE%8B%E3%81%88.jp/, which the DNS resolver cannot look up. Hostnames have to be converted to Punycode (https://xn--r8jz45g.jp/) using a separate library or the URL constructor, which now handles IDN conversion automatically when you read .hostname back out.

Patterns That Actually Work in Production

A handful of patterns cover almost every URL-building task in a modern JavaScript codebase. Lean on these and you can mostly stop thinking about which encoder to call.

URLSearchParams as the safer modern alternative

The single biggest upgrade is to stop concatenating query strings by hand. URLSearchParams was added to browsers and Node in 2017 and is universally available:

const u = new URL("https://api.example.com/search");
u.searchParams.set("q", "Café & Bar");
u.searchParams.set("page", "2");
fetch(u);

No encodeURIComponent call appears anywhere — but the encoding is correct, and the URL is parsed into structured pieces you can edit without string surgery. The same URL object also handles the path, the fragment, and the protocol.

Encoding a single template-literal interpolation

When you do need a one-liner, keep encodeURIComponent next to the variable, not next to the whole URL:

const slug = "café & co.";
const u =
  `https://example.com/posts/${
    encodeURIComponent(slug)
  }`;

That keeps the URL skeleton hand-written (no surprises) and only the variable bit encoded. The result is https://example.com/posts/caf%C3%A9%20%26%20co. — a single, correctly-escaped path segment.

Decoding safely — pairing every encode with a decode

If your code reads URLs that other code wrote, pair decodeURIComponent carefully. It throws on malformed input — a lone % or a truncated %2 — so wrap it in try/catch. Most production parsers fall back to the original string when decode fails, so the user sees a slightly ugly URL rather than a crash:

function safeDecode(s) {
  try { return decodeURIComponent(s); }
  catch { return s; }
}

For ad-hoc inspection — pasting a URL, decoding it, and seeing what the encoded bytes really were — running it through the iKit URL Encoder is faster than firing up DevTools. Everything stays in your browser, so confidential URLs from staging environments don't leave your machine. The same browser-local approach powers the iKit JSON Decoder and the iKit Base64 Encoder — useful neighbours when a URL contains JSON or Base64-encoded blobs that also need a look.

Related on iKit

Related posts