URL Encode JavaScript β encodeURIComponent()
Use the free online URL Encode Online directly in your browser β no install required.
Try URL Encode Online Online βWhen you build a search URL, pass a redirect path as a query parameter, or construct an OAuth authorization request, special characters like &, =, and spaces will silently corrupt the URL unless you URL encode them first. JavaScript offers three built-in approaches β encodeURIComponent(), encodeURI(), and URLSearchParamsβ each designed for a different use case, and choosing the wrong one is the root cause of most encoding bugs I've encountered in code reviews. For quick one-off encoding without any code, ToolDeck's URL Encoder handles it instantly in the browser. This guide covers all three approaches in depth (JavaScript ES2015+ / Node.js 10+): when to use each, how they differ for spaces and reserved characters, real-world Fetch and API patterns, CLI usage, and the four mistakes that cause the most subtle production bugs.
- βencodeURIComponent() is the right choice for encoding individual parameter values β it encodes every character except AβZ, aβz, 0β9, and - _ . ! ~ * ' ( )
- βencodeURI() encodes a complete URL while preserving structural characters (/ ? # & = :) β never use it for individual values
- βURLSearchParams auto-encodes key-value pairs using application/x-www-form-urlencoded format, where spaces become + instead of %20
- βDouble-encoding is the most common production bug: encodeURIComponent(encodeURIComponent(value)) turns %20 into %2520
- βThe qs library handles nested objects and arrays in query strings natively β stdlib URLSearchParams does not
What is URL Encoding?
URL encoding (formally called percent-encoding, defined in RFC 3986) converts characters that are not allowed or have special meaning in a URL into a safe representation. Each unsafe byte is replaced with a percent sign followed by two hexadecimal digits β the ASCII code of the character. A space becomes %20, an ampersand becomes %26, a forward slash becomes %2F.
The characters that are always safe and never encoded are called unreserved characters: letters AβZ and aβz, digits 0β9, and the four symbols - _ . ~. Everything else either must be encoded when used as data, or has a structural role in the URL itself (like / separating path segments or &separating query parameters). The practical consequence: a product name like βWireless Keyboard & Mouseβ in a query parameter destroys the URL structure if passed raw.
https://shop.example.com/search?q=Wireless Keyboard & Mouse&category=peripherals
https://shop.example.com/search?q=Wireless%20Keyboard%20%26%20Mouse&category=peripherals
encodeURIComponent() β The Right Function for Query Parameters
encodeURIComponent() is the workhorse of URL encoding in JavaScript. It encodes every character except the unreserved set (AβZ, aβz, 0β9, - _ . ! ~ * ' ( )). Critically, it encodes all characters that have structural meaning in URLs β &, =, ?, #, / β which makes it safe for use in parameter values. No import is needed; it is a global function available in all JavaScript environments.
Minimal working example
// Encode a search query parameter that contains special characters
const searchQuery = 'Wireless Keyboard & Mouse'
const filterStatus = 'in-stock'
const maxPrice = '149.99'
const searchUrl = `https://shop.example.com/products?` +
`q=${encodeURIComponent(searchQuery)}` +
`&status=${encodeURIComponent(filterStatus)}` +
`&maxPrice=${encodeURIComponent(maxPrice)}`
console.log(searchUrl)
// https://shop.example.com/products?q=Wireless%20Keyboard%20%26%20Mouse&status=in-stock&maxPrice=149.99Encoding a redirect URL as a query parameter
// The redirect destination is itself a URL β it must be fully encoded
// or the outer URL parser will misinterpret its ? and & as its own
const redirectAfterLogin = 'https://dashboard.internal/reports?view=weekly&team=platform'
const loginUrl = `https://auth.company.com/login?next=${encodeURIComponent(redirectAfterLogin)}`
console.log(loginUrl)
// https://auth.company.com/login?next=https%3A%2F%2Fdashboard.internal%2Freports%3Fview%3Dweekly%26team%3Dplatform
// Decoding on the receiving end
const params = new URLSearchParams(window.location.search)
const next = params.get('next') // Automatically decoded
const nextUrl = new URL(next!) // Safe to parse
console.log(nextUrl.hostname) // dashboard.internalEncoding non-ASCII and Unicode characters
// encodeURIComponent handles Unicode natively in all modern environments
// Each UTF-8 byte of the character is percent-encoded
const customerName = 'MΓΌller, Sophie'
const productTitle = 'ζ±δΊ¬ wireless adapter'
const reviewText = 'Muy bueno β funciona perfectamente'
console.log(encodeURIComponent(customerName))
// M%C3%BCller%2C%20Sophie
console.log(encodeURIComponent(productTitle))
// %E6%9D%B1%E4%BA%AC%20wireless%20adapter
console.log(encodeURIComponent(reviewText))
// Muy%20bueno%20%E2%80%94%20funciona%20perfectamente
// Decoding back
console.log(decodeURIComponent('M%C3%BCller%2C%20Sophie'))
// MΓΌller, SophieencodeURIComponent()uses the JavaScript engine's internal UTF-16 string encoding, then encodes each UTF-8 byte of the character separately. A character like ΓΌ (U+00FC) encodes to %C3%BC because its UTF-8 representation is two bytes: 0xC3 and 0xBC. This is correct and follows the URI standard β servers are expected to decode the UTF-8 byte sequence back to the original codepoint.JavaScript URL Encoding Functions β Character Reference
The three native encoding approaches differ in exactly which characters they encode. The table below shows the output for the most commonly problematic characters:
| Character | Role in URL | encodeURIComponent() | encodeURI() | URLSearchParams |
|---|---|---|---|---|
| Space | word separator | %20 | %20 | + |
| & | param separator | %26 | kept | %26 |
| = | key=value | %3D | kept | %3D |
| + | encoded space (form) | %2B | %2B | %2B |
| ? | query start | %3F | kept | %3F |
| # | fragment | %23 | kept | %23 |
| / | path separator | %2F | kept | %2F |
| : | scheme / port | %3A | kept | %3A |
| @ | auth separator | %40 | kept | %40 |
| % | percent literal | %25 | %25 | %25 |
| ~ | unreserved | kept | kept | kept |
The critical column is encodeURIComponent() vs encodeURI() for & and =: encodeURI() leaves them untouched, which is correct when encoding a full URL but catastrophic when encoding a value that contains these characters.
encodeURI() β When to Preserve URL Structure
encodeURI() is designed for encoding a complete URLβ it preserves all characters that are valid structural parts of a URI: the scheme (https://), the host, path separators, query delimiters, and the fragment identifier. Use it when you receive a URL that may contain spaces or non-ASCII characters in its path segments, but you need to keep its structure intact.
Sanitising a user-provided URL
// A URL pasted from a document with spaces in the path and non-ASCII chars const rawUrl = 'https://cdn.example.com/assets/product images/MΓΌnchen chair.png' const safeUrl = encodeURI(rawUrl) console.log(safeUrl) // https://cdn.example.com/assets/product%20images/M%C3%BCnchen%20chair.png // encodeURIComponent would break it β it encodes the :// and all / characters const broken = encodeURIComponent(rawUrl) console.log(broken) // https%3A%2F%2Fcdn.example.com%2Fassets%2Fproduct%20images%2FM%C3%BCnchen%20chair.png // β Not a valid URL β the scheme and slashes are destroyed
URL constructor is usually a better choice than encodeURI() for handling user-supplied URL strings β it normalises the URL, validates the structure, and gives you a clean API to access each component. Reserve encodeURI() for cases where you need a string result and already know the input is structurally a valid URL.URLSearchParams β The Modern Approach for Query Strings
URLSearchParams is the idiomatic way to build and parse query strings in modern JavaScript. It is available globally in all modern browsers and Node.js 10+, and handles encoding automatically β you work with plain key-value pairs and it produces the correct output. One important detail: it follows the application/x-www-form-urlencoded specification, which encodes spaces as + rather than %20. This is correct and widely supported, but you should be aware of it when your server expects a specific format.
Building a search request URL
// Building a search API URL with multiple parameters
const filters = {
query: 'standing desk',
category: 'office-furniture',
minPrice: '200',
maxPrice: '800',
inStock: 'true',
sortBy: 'price_asc',
}
const params = new URLSearchParams(filters)
const apiUrl = `https://api.example.com/v2/products?${params}`
console.log(apiUrl)
// https://api.example.com/v2/products?query=standing+desk&category=office-furniture&minPrice=200&maxPrice=800&inStock=true&sortBy=price_asc
// Appending additional params after construction
params.append('page', '2')
params.append('tag', 'ergonomic & adjustable')
console.log(params.toString())
// query=standing+desk&...&tag=ergonomic+%26+adjustableParsing an incoming query string
// Both browser (window.location.search) and Node.js (req.url) scenarios
function parseWebhookCallbackUrl(rawSearch: string) {
const params = new URLSearchParams(rawSearch)
return {
eventId: params.get('event_id'), // null if missing
timestamp: Number(params.get('ts')),
signature: params.get('sig'),
redirectUrl: params.get('redirect'), // Automatically decoded
tags: params.getAll('tag'), // Handles repeated keys
}
}
const callbackQuery = '?event_id=evt_9c2f&ts=1717200000&sig=sha256%3Dabc123&redirect=https%3A%2F%2Fdashboard.internal%2F&tag=payment&tag=webhook'
const parsed = parseWebhookCallbackUrl(callbackQuery)
console.log(parsed.redirectUrl) // https://dashboard.internal/
console.log(parsed.tags) // ['payment', 'webhook']How to URL-Encode Parameters in JavaScript Fetch Requests
The most common place URL encoding appears in production code is inside fetch() calls β either building the request URL or sending form data in the request body. Each scenario has its own correct approach.
GET request β encoding query parameters
async function searchInventory(params: {
sku?: string
warehouse: string
minStock: number
updatedAfter?: Date
}): Promise<{ items: unknown[]; total: number }> {
const searchParams = new URLSearchParams()
if (params.sku) searchParams.set('sku', params.sku)
searchParams.set('warehouse', params.warehouse)
searchParams.set('min_stock', String(params.minStock))
if (params.updatedAfter) searchParams.set('updated_after', params.updatedAfter.toISOString())
const url = `https://inventory.internal/api/items?${searchParams}`
const res = await fetch(url, {
headers: {
'Authorization': `Bearer ${process.env.INVENTORY_API_KEY}`,
'Accept': 'application/json',
},
})
if (!res.ok) {
throw new Error(`Inventory API ${res.status}: ${await res.text()}`)
}
return res.json()
}
const results = await searchInventory({
warehouse: 'eu-west-1',
minStock: 10,
updatedAfter: new Date('2025-01-01'),
})
console.log(`Found ${results.total} items`)POST request β URL-encoding a form body
// application/x-www-form-urlencoded POST β used by OAuth token endpoints,
// legacy form-submission APIs, and some webhook providers
async function requestOAuthToken(
clientId: string,
clientSecret: string,
code: string,
redirectUri: string,
): Promise<{ access_token: string; expires_in: number }> {
const body = new URLSearchParams({
grant_type: 'authorization_code',
client_id: clientId,
client_secret: clientSecret,
code,
redirect_uri: redirectUri, // URLSearchParams encodes this automatically
})
const res = await fetch('https://oauth.provider.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString(),
// body: body β also works directly in modern environments (Fetch spec accepts URLSearchParams)
})
if (!res.ok) {
const err = await res.json()
throw new Error(`OAuth error: ${err.error_description ?? err.error}`)
}
return res.json()
}When you need to test or debug an encoded URL without setting up a script, paste the raw value directly into the URL Encoder β it encodes and decodes instantly, showing you exactly what the browser and server will see. Useful for inspecting OAuth redirect URIs, webhook callback URLs, and CDN signed request parameters.
Command-Line URL Encoding with Node.js and Shell
For shell scripts, CI pipelines, or quick debugging, several command-line approaches work without writing a full script.
# ββ Node.js one-liners β cross-platform (macOS, Linux, Windows) ββββββββ
# encodeURIComponent β encode a single value
node -e "console.log(encodeURIComponent(process.argv[1]))" "Wireless Keyboard & Mouse"
# Wireless%20Keyboard%20%26%20Mouse
# Build a complete query string
node -e "console.log(new URLSearchParams(JSON.parse(process.argv[1])).toString())" '{"q":"standing desk","category":"office-furniture","page":"1"}'
# q=standing+desk&category=office-furniture&page=1
# Decode a percent-encoded string
node -e "console.log(decodeURIComponent(process.argv[1]))" "Wireless%20Keyboard%20%26%20Mouse"
# Wireless Keyboard & Mouse
# ββ curl β automatic encoding βββββββββββββββββββββββββββββββββββββββββββ
# curl --data-urlencode encodes values automatically in GET and POST
curl -G "https://api.example.com/search" --data-urlencode "q=Wireless Keyboard & Mouse" --data-urlencode "category=office furniture"
# ββ Python one-liner (available on most systems) βββββββββββββββββββββββββ
python3 -c "from urllib.parse import quote; print(quote(input(), safe=''))"
# (type the string and press Enter)
# ββ jq + bash: encode every value in a JSON object βββββββββββββββββββββββ
echo '{"q":"hello world","tag":"node & express"}' | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); console.log(new URLSearchParams(d).toString())"
# q=hello+world&tag=node+%26+expressHigh-Performance Alternative: qs
The built-in URLSearchParams does not support nested objects or arrays β the shape of query strings used by many APIs and frameworks. The qs library (30M+ weekly npm downloads) handles the full range of query string patterns used in the wild: nested objects with bracket notation (filters[status]=active), repeated keys, custom encoders, and configurable array serialisation formats.
npm install qs # or pnpm add qs
import qs from 'qs'
// URLSearchParams cannot represent this structure natively
const reportFilters = {
dateRange: {
from: '2025-01-01',
to: '2025-03-31',
},
status: ['published', 'archived'],
author: { id: 'usr_4f2a9c1b', role: 'editor' },
workspace: 'ws-platform-eu',
}
// qs produces bracket-notation query strings used by Express, Rails, Django REST
const query = qs.stringify(reportFilters, { encode: true })
console.log(query)
// dateRange%5Bfrom%5D=2025-01-01&dateRange%5Bto%5D=2025-03-31&status%5B0%5D=published&status%5B1%5D=archived&author%5Bid%5D=usr_4f2a9c1b&author%5Brole%5D=editor&workspace=ws-platform-eu
// Human-readable (no encoding) β useful for debugging
console.log(qs.stringify(reportFilters, { encode: false }))
// dateRange[from]=2025-01-01&dateRange[to]=2025-03-31&status[0]=published&status[1]=archived...
// Parsing back
const parsed = qs.parse(query)
console.log(parsed.dateRange) // { from: '2025-01-01', to: '2025-03-31' }
console.log(parsed.status) // ['published', 'archived']For flat key-value parameters, URLSearchParams is always the right choice β it is built-in, has zero overhead, and is universally supported. Reach for qs only when you need nested structures, array serialisation formats other than repeated keys, or you are integrating with a back-end framework that expects bracket-notation query strings.
Common Mistakes
I've seen these four mistakes repeatedly in production codebases β most are silent failures that only surface when a value contains a special character, which is exactly the kind of bug that slips through unit tests and only appears with real user data.
Mistake 1 β Using encodeURI() for query parameter values
Problem: encodeURI() does not encode &, =, or +. When a value contains these characters, they are interpreted as query string syntax, silently splitting or overwriting parameters. Fix: always use encodeURIComponent() for individual values.
// β encodeURI does not encode & and = in the value
// "plan=pro&promo=SAVE20" is treated as two separate params
const planName = 'Pro Plan (save=20% & free trial)'
const url = `/checkout?plan=${encodeURI(planName)}`
// /checkout?plan=Pro%20Plan%20(save=20%%20&%20free%20trial)
// β & breaks the query string// β
encodeURIComponent encodes & and = safely
const planName = 'Pro Plan (save=20% & free trial)'
const url = `/checkout?plan=${encodeURIComponent(planName)}`
// /checkout?plan=Pro%20Plan%20(save%3D20%25%20%26%20free%20trial)
// β = and & are both encodedMistake 2 β Double-encoding an already-encoded string
Problem: Calling encodeURIComponent() on a value that is already percent-encoded turns the % into %25, so %20 becomes %2520. The server decodes once and receives a literal %20 instead of a space. Fix: decode first, then re-encode, or ensure the value is never encoded twice.
// β encodedParam already contains %20 β encoding again produces %2520
const encodedParam = 'Berlin%20Office'
const url = `/api/locations/${encodeURIComponent(encodedParam)}`
// /api/locations/Berlin%2520Office
// Server sees: "Berlin%20Office" (a literal percent-twenty, not a space)// β
Decode first if the value may already be encoded
const maybeEncoded = 'Berlin%20Office'
const clean = decodeURIComponent(maybeEncoded) // 'Berlin Office'
const url = `/api/locations/${encodeURIComponent(clean)}`
// /api/locations/Berlin%20Office β correctMistake 3 β Not accounting for the + / %20 difference between URLSearchParams and encodeURIComponent
Problem: URLSearchParams encodes spaces as + (application/x-www-form-urlencoded), while encodeURIComponent() uses %20. Mixing both in the same URL β for example, appending a pre-encoded string to a URLSearchParams output β produces inconsistent encoding that confuses some parsers. Fix: pick one approach and use it consistently throughout a URL-building function.
// β Mixed: URLSearchParams uses + for spaces, but we're appending
// a manually-encoded segment that uses %20
const base = new URLSearchParams({ category: 'office furniture' })
const extra = `sort=${encodeURIComponent('price asc')}`
const url = `/api/products?${base}&${extra}`
// /api/products?category=office+furniture&sort=price%20asc
// β two different space encodings in the same URL// β
Use URLSearchParams exclusively β consistent encoding throughout
const params = new URLSearchParams({
category: 'office furniture',
sort: 'price asc',
})
const url = `/api/products?${params}`
// /api/products?category=office+furniture&sort=price+ascMistake 4 β Forgetting to encode path segments that contain slashes
Problem: A resource identifier like a file path (reports/2025/q1.pdf) used as a REST path segment contains / characters that the server router interprets as path separators, routing to a non-existent endpoint. Fix: always use encodeURIComponent() for path segments that may contain slashes.
// β The file path contains / β the server receives a 3-segment path
// instead of a single resource ID
const filePath = 'reports/2025/q1-financials.pdf'
const url = `https://storage.example.com/objects/${filePath}`
// https://storage.example.com/objects/reports/2025/q1-financials.pdf
// β Server routes to /objects/:year/:filename β 404 or wrong resource// β
encodeURIComponent encodes / as %2F β single path segment
const filePath = 'reports/2025/q1-financials.pdf'
const url = `https://storage.example.com/objects/${encodeURIComponent(filePath)}`
// https://storage.example.com/objects/reports%2F2025%2Fq1-financials.pdf
// β Server receives the full file path as one resource identifierJavaScript URL Encoding Methods β Quick Comparison
| Method | Encodes spaces as | Encodes & = ? | Encodes # / : | Use case | Requires install |
|---|---|---|---|---|---|
| encodeURIComponent() | %20 | β yes | β yes | Encoding individual parameter values | No |
| encodeURI() | %20 | β no | β no | Sanitising a complete URL string | No |
| URLSearchParams | + | β yes | β yes | Building and parsing query strings | No |
| URL constructor | auto by component | auto | auto | Constructing and normalising full URLs | No |
| qs library | %20 (configurable) | β yes | β yes | Nested objects and arrays in query strings | npm install qs |
For the vast majority of cases, the choice reduces to three scenarios. Use URLSearchParams when constructing multi-parameter query strings from structured data β it is the safest and most readable option. Use encodeURIComponent() for encoding a single value in a template literal URL, for path segments, or for values in systems that expect %20 instead of + for spaces (such as AWS S3 signed URLs). Reach for qs only when your query string carries nested objects or arrays that URLSearchParams cannot represent natively.
Frequently Asked Questions
Related Tools
For a one-click encode or decode without writing any code, paste your string directly into the URL Encoder β it handles percent-encoding and decoding instantly in your browser, with the encoded output ready to copy into a fetch call or terminal.
Alex is a front-end and Node.js developer with extensive experience building web applications and developer tooling. He is passionate about web standards, browser APIs, and the JavaScript ecosystem. In his spare time he contributes to open-source projects and writes about modern JavaScript patterns, performance optimisation, and everything related to the web platform.
Sophie is a full-stack developer focused on TypeScript across the entire stack β from React frontends to Express and Fastify backends. She has a particular interest in type-safe API design, runtime validation, and the patterns that make large JavaScript codebases stay manageable. She writes about TypeScript idioms, Node.js internals, and the ever-evolving JavaScript module ecosystem.