encodeURI vs encodeURIComponent: How to Encode Query Parameters Correctly

URL encoding bugs usually appear when a parameter contains &, =, ?, #, spaces, non-English text, or another URL. The most common JavaScript mistake is using encodeURI() when the value should be encoded with encodeURIComponent().

The short rule:

  • use encodeURI() for a complete URL
  • use encodeURIComponent() for a parameter value or path segment

Why the difference matters

encodeURI() preserves characters that have meaning in a full URL, such as:

: / ? # & =

That is useful when you already have a complete URL and only need to escape spaces or non-ASCII characters.

encodeURIComponent() encodes more characters because it is meant for one component of a URL, such as a query parameter value.

If a parameter value contains &, =, or #, encodeURI() can break the query string.

Query parameter example

Suppose you want:

const search = 'a=b & c=d'

Wrong:

const url = '/search?q=' + encodeURI(search)

The & may be interpreted as a new query parameter separator.

Right:

const url = '/search?q=' + encodeURIComponent(search)

This keeps the whole value inside q.

Encoding a URL inside another URL

Redirect parameters are a common source of bugs.

const next = 'https://example.com/a?x=1&y=2'
const loginUrl = '/login?next=' + encodeURIComponent(next)

If you use encodeURI(next), the nested &y=2 can become part of the outer /login query string instead of staying inside next.

Path segment example

For a path segment, use encodeURIComponent():

const username = 'Ada Lovelace/notes'
const url = '/users/' + encodeURIComponent(username)

The slash is part of the username, not a path separator, so it must be encoded.

If you use encodeURI() here, the slash remains and changes the route structure.

Prefer URL and URLSearchParams

For query strings, modern JavaScript gives you safer APIs:

const url = new URL('https://example.com/search')
url.searchParams.set('q', 'a=b & c=d')
url.searchParams.set('lang', 'zh-CN')

console.log(url.toString())

URLSearchParams handles encoding and separators for you. It also reduces the chance of double encoding.

Full URL, query value, path segment, and hash

Many encoding bugs happen because the same string passes through several URL layers. Treat each layer separately:

SituationSafer choiceWhy
Complete URLencodeURI()Keeps :, /, ?, &, and # meaningful
Query parameter valueencodeURIComponent() or URLSearchParamsEncodes &, =, ?, and # inside the value
Path segmentencodeURIComponent()Encodes / so it does not become another route level
Hash valueencodeURIComponent() for custom valuesPrevents accidental nested fragments
Form-style query stringURLSearchParamsHandles + and separator rules consistently

For example, a full URL can contain a query string, and a query string can contain another URL:

const target = 'https://example.com/report?range=7d&format=json#summary'
const share = new URL('https://app.example.com/share')
share.searchParams.set('next', target)

console.log(share.toString())

In this case you do not need to manually call encodeURIComponent() because URLSearchParams encodes the next value for you. If you build the string by concatenation, you must encode the nested URL yourself.

Backend decoding rules

The frontend and backend must agree on which layer is encoded. A common bug is decoding the whole URL before parsing the query string:

// Risky: decoding the whole URL can turn encoded separators back into real separators.
const decoded = decodeURIComponent('/search?q=a%3Db%26c%3Dd')

After decoding, a%3Db%26c%3Dd becomes a=b&c=d, and a later parser may treat &c=d as a separate parameter.

The safer order is:

  1. Parse the URL into components.
  2. Read a single query parameter value.
  3. Decode that one value once.
  4. If the decoded value contains JSON, Base64, or another URL, process that inner format separately.

This matters for OAuth redirects, password reset links, invite links, search filters, analytics campaign URLs, and any API endpoint that accepts a callback URL.

Double encoding

Double encoding happens when you encode an already encoded value.

hello world
hello%20world
hello%2520world

%25 is the encoded form of %. If you see %2520, a value that already contained %20 was encoded again.

Before decoding or encoding, ask which layer you are working on:

  • raw user input
  • query parameter value
  • full URL
  • JSON payload containing a URL
  • redirect URL inside another URL

Spaces: %20 vs +

In URLs, spaces are commonly encoded as %20. In form-encoded query strings, spaces may appear as +.

Do not manually replace spaces unless you know the target format. URLSearchParams is usually the safest option for query strings generated in JavaScript.

SEO and analytics parameters

URL encoding also affects SEO and analytics. A malformed query string can make two URLs look different when they should be the same, or make campaign parameters disappear before analytics receives them.

Examples to test:

  • utm_campaign=summer sale should be encoded consistently.
  • A landing page URL inside redirect= should not leak its own utm_* parameters into the outer URL.
  • A canonical URL should not contain a double-encoded path such as %252F.
  • A search page should avoid creating endless crawlable combinations of broken query parameters.

If you are debugging traffic attribution, compare the raw URL, the parsed query parameters, and the decoded value. Do not decode the entire URL repeatedly.

Debugging checklist

When a URL parameter breaks:

  1. Identify whether you are encoding a full URL or one component.
  2. Use encodeURIComponent() for query values.
  3. Use URLSearchParams when building query strings.
  4. Check for double encoding such as %252F or %2520.
  5. Decode one layer at a time.
  6. Be careful with nested redirect URLs.
  7. Test values containing &, =, ?, #, spaces, Chinese/Japanese text, and emoji.

Use the URL Encoder/Decoder to inspect a single value and the URL Parameter Parser to see how a full query string is split. If a parameter contains JSON, validate it with the JSON Formatter after decoding the URL layer.