How to Encode a URL in JavaScript — encodeURI vs encodeURIComponent Complete Guide

Every JavaScript developer eventually hits this moment.

You build a URL with dynamic parameters, test it locally, and everything works. Then production traffic hits — and suddenly the backend receives corrupted data.

A query parameter gets silently truncated. An OAuth redirect fails with invalid_request. A pagination link goes to the wrong page.

At the center of it all is usually one function choice:

encodeURI() vs encodeURIComponent()

They sound interchangeable. They almost behave the same in simple examples. But in real systems, picking the wrong one quietly breaks URLs in ways that are hard to debug.

This guide covers everything you need to know about URL encoding in JavaScript — the functions, the edge cases, the production bugs, and the patterns that prevent them.


Why URL Encoding Matters

URLs are not plain text. Characters like :, /, ?, &, =, and # have structural meaning inside a URL.

When you include user-generated content — search queries, email addresses, file names, tags — these characters can break the URL structure.

URL encoding (percent-encoding) solves this by converting unsafe characters into a safe hex format.

For example:

hello world

becomes:

hello%20world

And:

email=john@example.com&name=doe

Without proper encoding, the @ and & cause parsing issues.

If you'd like to inspect encoded payloads quickly while reading, the URL Encoder/Decoder tool is useful for spot-checking examples.


The Two Functions

JavaScript provides two primary URL encoding functions:

FunctionPurpose
encodeURI()Encode an entire URL
encodeURIComponent()Encode a single URL component

The names suggest the usage. But developers frequently use them incorrectly because the difference isn't obvious until it breaks.


What encodeURI() Preserves

encodeURI() assumes the input is a full URL. It encodes unsafe characters but intentionally leaves URL structure characters untouched.

const url = "https://example.com/search?q=hello world & code";

console.log(encodeURI(url));

Output:

https://example.com/search?q=hello%20world%20&%20code

Characters encodeURI() preserves:

: / ? & = #

These are the building blocks of a URL. encodeURI() trusts that your URL structure is already correct.


What encodeURIComponent() Encodes

encodeURIComponent() assumes the input is only one piece of a URL. It is much more aggressive.

const searchTerm = "hello world & code";

console.log(encodeURIComponent(searchTerm));

Output:

hello%20world%20%26%20code

Note that & became %26. The space became %20. This is safe to embed anywhere in a URL — query string, path segment, fragment, you name it.

Characters encodeURIComponent() preserves:

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

Everything else gets percent-encoded.


The Production Bug That Hits Everyone

This is the most common URL encoding mistake in JavaScript applications.

Broken Version

const searchQuery = "laptops & tablets";
const url = `https://api.example.com/products?q=${encodeURI(searchQuery)}`;

fetch(url);

Output URL:

https://api.example.com/products?q=laptops%20&%20tablets

This looks somewhat safe. But the backend may interpret it as:

q=laptops
tablets=<empty>

Because & is the query parameter separator.

Correct Version

const searchQuery = "laptops & tablets";
const url = `https://api.example.com/products?q=${encodeURIComponent(searchQuery)}`;

fetch(url);

Output URL:

https://api.example.com/products?q=laptops%20%26%20tablets

Now the entire value is safe. One parameter. One value. No silent truncation.


When to Use encodeURI()

Use encodeURI() only when you have a structurally complete URL that needs minor cleanup.

Good use cases:

  • Taking a valid URL from an environment variable
  • Normalizing a URL that may contain spaces or unicode
  • Building a URL where you trust the component values are already encoded
const baseUrl = "https://example.com/api";
const fullUrl = encodeURI(`${baseUrl}?name=John Doe`);

Be careful here — if any of the interpolated values contain &, =, or ?, this breaks.


When to Use encodeURIComponent()

Use encodeURIComponent() for every dynamic value you insert into a URL.

function buildSearchUrl(base, params) {
  const query = Object.entries(params)
    .map(([key, value]) =>
      `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
    )
    .join("&");

  return `${base}?${query}`;
}

This pattern is safe for any user input.


OAuth Redirect URLs — Where This Bites Hardest

OAuth integrations are the number one place developers encounter this bug.

The Problem

const redirectUri = "https://myapp.com/callback?source=web";
const authUrl = `https://auth.com/oauth/authorize?redirect_uri=${redirectUri}`;

Output:

https://auth.com/oauth/authorize?redirect_uri=https://myapp.com/callback?source=web

The second ? breaks the entire OAuth flow. The provider sees redirect_uri=https://myapp.com/callback and then source=web as a separate parameter.

The Fix

const redirectUri = encodeURIComponent(
  "https://myapp.com/callback?source=web"
);

const authUrl = `https://auth.com/oauth/authorize?redirect_uri=${redirectUri}`;

Output:

https://auth.com/oauth/authorize?redirect_uri=https%3A%2F%2Fmyapp.com%2Fcallback%3Fsource%3Dweb

Now redirect_uri is a single parameter. The OAuth provider can parse it correctly.

This exact bug shows up in Google OAuth, GitHub OAuth, Stripe, Slack, and dozens of other integrations.


Double Encoding — The Silent Killer

Double encoding happens when you encode a value that is already encoded.

const value = "hello world";
const once = encodeURIComponent(value);  // hello%20world
const twice = encodeURIComponent(once);  // hello%2520world

The second call encodes the % sign into %25. The result is %2520 instead of %20.

How It Occurs in Practice

// Middleware that encodes all parameters
function encodeParams(params) {
  return Object.fromEntries(
    Object.entries(params).map(([k, v]) => [k, encodeURIComponent(v)])
  );
}

// Then later, code that also encodes
const url = `/api?q=${encodeURIComponent(params.q)}`; // DOUBLE ENCODED

How to Avoid It

Establish a clear boundary in your application:

  • Decode early — decode values as soon as they enter your system
  • Encode late — encode values right before they leave your system

Never encode something unless you know it is currently in a raw state.


Modern Alternative: URL and URLSearchParams

Modern JavaScript provides better APIs that reduce manual encoding mistakes.

Using URLSearchParams

const params = new URLSearchParams({
  q: "laptops & tablets",
  category: "electronics",
  page: 1
});

const url = `https://api.example.com/products?${params.toString()}`;
console.log(url);

Output:

https://api.example.com/products?q=laptops+%26+tablets&category=electronics&page=1

Note that URLSearchParams uses + for spaces (form-urlencoded convention), not %20. This is usually fine for query parameters.

Using URL Constructor

const url = new URL("https://api.example.com/products");
url.searchParams.set("q", "laptops & tablets");
url.searchParams.set("category", "electronics");

console.log(url.toString());

This is the safest approach. The URL class handles all encoding automatically.


Encoding Path Segments

Query parameters aren't the only thing that needs encoding. Path segments with special characters also need care.

const productName = "laptops & tablets/2024";
const url = `/products/${encodeURIComponent(productName)}`;

Without encoding, the / inside productName would be interpreted as a path separator, breaking routing.


Unicode and Emoji Encoding

JavaScript encodes non-ASCII characters as UTF-8 bytes.

console.log(encodeURIComponent("東京"));
console.log(encodeURIComponent("🔥"));

Output:

%E6%9D%B1%E4%BA%AC
%F0%9F%94%A5

Most modern backends handle UTF-8 correctly. If you encounter issues, check that your backend framework is not defaulting to Latin-1 or ISO-8859-1.


Decoding: decodeURI() and decodeURIComponent()

The decoding counterparts follow the same logic:

decodeURI(encodedFullUrl);
decodeURIComponent(encodedComponent);

Common Decoding Mistake

// Trying to decode individual params with decodeURI
const value = decodeURI("%26");

decodeURI() will NOT decode %26 because & is a reserved URL character. Use decodeURIComponent() for individual parameter values.


Browser URL Encoding Behavior

Browsers add another layer of complexity.

When you type a URL into the address bar, the browser may auto-encode certain characters. When JavaScript reads window.location.href, the value may already be partially encoded.

// User types: https://example.com/search?q=hello world
console.log(window.location.href);
// Browser may auto-encode space

This means reading and re-encoding from location.href can produce double encoding. Always read from URLSearchParams instead.


Encoding in Node.js

Node.js behaves identically to browser JavaScript for encodeURI and encodeURIComponent.

const http = require("http");
const query = "node.js & express";

http.get(
  `http://api.example.com/search?q=${encodeURIComponent(query)}`,
  (res) => { /* handle response */ }
);

For building URLs in Node.js, the url module's URL and URLSearchParams constructors work exactly like the browser versions.


Debugging URL Encoding Issues

When something looks wrong, follow this process:

1. Log the Raw URL

console.log("Raw request URL:", url);
// Inspect the actual encoded string

2. Decode Layer by Layer

const suspect = "hello%2520world";
const first = decodeURIComponent(suspect);  // hello%20world
const second = decodeURIComponent(first);   // hello world

3. Compare Browser and Backend

Use the same input values to compare:

  • Browser DevTools Network tab
  • Curl requests from terminal
  • Backend access logs

4. Test Edge Case Characters

Always test with:

& = ? # / % + space emoji unicode

These are where encoding bugs hide.


Best Practices Summary

Encode Individual Values, Not Full URLs

// Good
`?q=${encodeURIComponent(value)}`

// Bad
encodeURI(`?q=${value}`)

Prefer URLSearchParams for Query Strings

const params = new URLSearchParams({ key: value });
const url = `/api?${params}`;

Never Double-Encode

Check the encoding state of your data before encoding again.

Decode at System Boundary

// Express middleware example
app.use((req, res, next) => {
  req.query = Object.fromEntries(
    Object.entries(req.query).map(([k, v]) => [k, decodeURIComponent(v)])
  );
  next();
});

Use the Correct Decode Function

  • decodeURI() for full URLs
  • decodeURIComponent() for individual values

Related Resources

If you are working with query parameters and URL construction, these guides expand on related topics:


FAQ

What is the difference between encodeURI() and encodeURIComponent()?

encodeURI() encodes a full URL and preserves structure characters like :, /, ?, &, =. encodeURIComponent() encodes a single URL component and encodes every special character.

Which function should I use for query parameter values?

Always use encodeURIComponent() for query parameter values.

Can I use encodeURIComponent() on a full URL?

Technically yes, but it will encode the URL structure characters (:, /, ?, &, =) and break the URL.

Why do spaces become %20 in encodeURIComponent() but + in URLSearchParams?

encodeURIComponent() follows RFC 3986 (standard URL encoding), which encodes spaces as %20. URLSearchParams follows the application/x-www-form-urlencoded specification, which encodes spaces as +.

What causes double URL encoding?

Encoding an already-encoded value a second time. The % character itself gets encoded into %25.

Is URLSearchParams better than manual encoding?

Yes, for most cases. It eliminates manual encoding mistakes and automatically handles edge cases.

How do I decode URL-encoded values?

Use decodeURIComponent() for individual encoded values and decodeURI() for full encoded URLs.


Final Thoughts

URL encoding in JavaScript is one of those topics that seems trivial until it breaks a production deployment.

The mental model is simple: treat encodeURI() as a full-URL-safe function and encodeURIComponent() as a value-safe function. Use URLSearchParams when you can, and never encode blindly without knowing the current state of your data.

The next time you see a cryptic 400 Bad Request or invalid redirect_uri in your logs, check the encoding. Nine times out of ten, that is where the bug lives.

For quick testing during debugging sessions, keep the URL Encoder/Decoder tool handy. It helps spot encoding issues faster than staring at raw logs.