Practical patterns for URL-encoding query parameters, with code examples and the four most common mistakes that break URLs in production. Covers arrays, nested data, embedded URLs, and cURL commands.
Encoding URL parameters is the single most common URL-encoding task. Every API call, every form submission, every search box does it. This guide covers the right way to encode parameters for each common situation.
Encode each value separately, then assemble the URL. Never encode the entire URL as one blob — that destroys the structural characters (?, &, =) that the parser needs.
// JavaScript — manual
const baseUrl = 'https://api.example.com/search';
const q = encodeURIComponent('Hello, World!');
const lang = encodeURIComponent('en-US');
const url = `${baseUrl}?q=${q}&lang=${lang}`;
// → 'https://api.example.com/search?q=Hello%2C%20World!&lang=en-US'
// JavaScript — preferred (URLSearchParams handles encoding)
const params = new URLSearchParams({
q: 'Hello, World!',
lang: 'en-US'
});
const url = `https://api.example.com/search?${params}`;
// → 'https://api.example.com/search?q=Hello%2C+World%21&lang=en-US'
// Wrong — produces a broken non-URL
encodeURIComponent('https://example.com/search?q=hello');
// → 'https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dhello'
// The colons, slashes, ?, and = are all encoded — this is no longer a URL.
// Right — only encode the value
const baseUrl = 'https://example.com/search';
const q = encodeURIComponent('hello');
const url = `${baseUrl}?q=${q}`;
// Wrong — breaks if userInput contains & or =
const url = `https://api.example.com/search?q=${userInput}`;
// If userInput is 'rock & roll', the URL becomes:
// 'https://api.example.com/search?q=rock & roll'
// — the & is interpreted as a parameter separator
// Right
const url = `https://api.example.com/search?q=${encodeURIComponent(userInput)}`;
// → 'https://api.example.com/search?q=rock%20%26%20roll'
// Wrong — user input is already encoded once
const userInput = 'Hello%20World'; // already encoded
const url = `${baseUrl}?q=${encodeURIComponent(userInput)}`;
// → '...?q=Hello%2520World' ← the % got encoded again
// Right — decode incoming data first, or treat it as already-prepared
const url = `${baseUrl}?q=${userInput}`; // if already encoded, leave it
// Wrong — uses query encoding (+) for path components
const segment = encodeURIComponent('Hello World').replace(/%20/g, '+');
const url = `https://example.com/users/${segment}/profile`;
// → 'https://example.com/users/Hello+World/profile'
// The + means literal + in a path, not space.
// Right
const segment = encodeURIComponent('Hello World');
const url = `https://example.com/users/${segment}/profile`;
// → 'https://example.com/users/Hello%20World/profile'
Many APIs support multiple values for the same key. Three common conventions:
// ?tags=javascript&tags=url&tags=encoding
const params = new URLSearchParams();
['javascript', 'url', 'encoding'].forEach(tag => params.append('tags', tag));
Express.js, Ruby on Rails, and most modern web frameworks parse this as an array.
// ?tags=javascript,url,encoding
const tagString = ['javascript', 'url', 'encoding'].join(',');
const url = `?tags=${encodeURIComponent(tagString)}`;
// → '?tags=javascript%2Curl%2Cencoding'
// ?tags[]=javascript&tags[]=url&tags[]=encoding
// In JS, manually:
const params = new URLSearchParams();
['javascript', 'url', 'encoding'].forEach(tag => params.append('tags[]', tag));
The URL-encoded format has no native support for nested data, but conventions exist. The most common is bracket notation:
// Goal: { user: { name: 'Alice', age: 30 } }
// Encoded:
?user[name]=Alice&user[age]=30
// Or with dot notation (Spring, some others):
?user.name=Alice&user.age=30
// Or JSON-encoded value (works everywhere but ugly):
?user=%7B%22name%22%3A%22Alice%22%2C%22age%22%3A30%7D
OAuth callbacks, redirect parameters, and webhook URLs often need to embed one URL inside another. The inner URL must be fully encoded:
const targetUrl = 'https://example.com/page?ref=ad';
const redirectUrl = `https://auth.example.com/login?next=${encodeURIComponent(targetUrl)}`;
// → 'https://auth.example.com/login?next=https%3A%2F%2Fexample.com%2Fpage%3Fref%3Dad'
// Wrong — leaves the inner URL’s ? and = unencoded
const redirectUrl = `https://auth.example.com/login?next=${targetUrl}`;
// → '...?next=https://example.com/page?ref=ad'
// The second ? is ambiguous; some parsers will see two query strings
cURL has --data-urlencode built in, which handles encoding for you:
# GET request with encoded query
curl --data-urlencode "q=Hello, World!" -G https://api.example.com/search
# → https://api.example.com/search?q=Hello%2C%20World%21
# POST request with form-encoded body
curl -X POST https://api.example.com/submit \
--data-urlencode "name=John Doe" \
--data-urlencode "message=Hi there!"
Before sending any URL with parameters:
✓ Each value uses the right encoding function for your language
✓ Structural characters (?, &, =) are NOT encoded — they’re part of the URL syntax
✓ Reserved characters in values (&, =, +, #) ARE encoded
✓ Non-ASCII characters are encoded as UTF-8 (this is the default in modern libraries)
✓ The URL hasn’t been double-encoded somewhere upstream
Only the parameter values. The structural parts of the URL (the protocol, host, path separators, ? and & and = symbols) must stay as literal characters. Encoding the whole URL turns it into an encoded string that's no longer a usable URL.
Three common conventions: repeated keys (?tags=a&tags=b — most frameworks accept this); comma-separated values (?tags=a,b,c — older REST APIs); PHP bracket notation (?tags[]=a&tags[]=b — PHP/Laravel and some others). Check the receiving API docs.
Yes, by URL-encoding the JSON string: encodeURIComponent(JSON.stringify({key: "value"})). It works but is ugly and bumps URL length. For anything but trivial cases, send the JSON as a POST body instead.
The & wasn't encoded. In query strings, & is a reserved character that separates parameters. If your value contains a literal ampersand, it must be encoded as %26 — otherwise the parser sees two parameters where you intended one.
Both work in query strings — most servers accept either as a space. + is the legacy form-encoded convention (used by HTML form submissions); %20 is the RFC 3986 canonical form. In path components, only %20 means space; + is a literal plus.