When you put user-entered text into a URL, should it go in the path or the query string—and how do you encode it without changing the URL’s structure? The answer depends on what the value represents. This guide compares both options and shows how to encode each safely in JavaScript.
Key Takeaways
- Put a value in a path segment when it identifies a resource; put it in a query parameter when it modifies a request, such as a search or filter.
- Encode a path value as one segment, not as an entire URL. Use
encodeURIComponentfor a raw segment andURLSearchParamsfor query parameters. - Keep raw user input separate from encoded URL text. Encoding an already encoded value usually encodes its
%signs again. - Encoding preserves URL structure; it does not validate input, grant access, or make a value safe for every server and proxy.
Choose a URL component for its meaning
A URL’s path and query string have different jobs. For example:
https://shop.example/products/green-tea?currency=EUR
Here, green-tea identifies a product in the path. currency=EUR changes how the request is presented, without identifying a different product.
A useful rule of thumb:
| Use | Typical purpose | Example |
|---|---|---|
| Path segment | Identify a resource or a part of a hierarchy | /users/42, /products/green-tea |
| Query parameter | Search, filter, sort, paginate, or otherwise modify a request | /products?category=tea&page=2 |
This is a design convention, not a security boundary. A server must still check whether a requester is allowed to access a resource, regardless of whether its identifier appears in the path or query.
Encode path input as one segment
A path contains separators such as /. If user input is meant to be one segment, encode that input separately before adding it to the path. In JavaScript, encodeURIComponent is usually the appropriate starting point for a raw segment:
const base = new URL("https://example.com/");
const userInput = "alice/smith?role=admin";
const profileUrl = new URL(
`/users/${encodeURIComponent(userInput)}`,
base
);
console.log(profileUrl.href);
// https://example.com/users/alice%2Fsmith%3Frole%3Dadmin
The slash and question mark in the input are percent-encoded, so they do not become a path separator or start a query string in the constructed URL. Encode each dynamic segment separately; do not encode the entire path, because doing so would also encode the separators that your route needs.
Some edge cases need special attention:
- Spaces:
encodeURIComponent("green tea")producesgreen%20tea. - Reserved characters: characters such as
/,?, and#have structural meaning in URLs. Encoding a raw segment prevents them from acting as separators in the URL string. - Unicode: JavaScript encodes Unicode text as UTF-8 bytes represented by percent escapes. For example,
encodeURIComponent("café")producescaf%C3%A9. - Dots:
encodeURIComponent("..")leaves the dots unchanged. A URL parser or server may treat.and..as path-navigation segments. Reject these values or handle them deliberately if your identifier rules allow them. - Encoded slashes:
%2Fmay be decoded or handled specially by servers, proxies, and routers. Do not assume every layer will preserve it as data inside one segment. If identifiers can contain slashes, test the complete deployed routing stack or choose a representation that avoids ambiguous separators.
Encode query values with URLSearchParams
For query parameters, use the URL API and URLSearchParams rather than joining user input into a query string by hand:
const searchUrl = new URL("https://example.com/search");
searchUrl.searchParams.set("q", "C++ & tea ☕");
searchUrl.searchParams.set("page", "2");
console.log(searchUrl.href);
// https://example.com/search?q=C%2B%2B+%26+tea+%E2%98%95&page=2
URLSearchParams serializes the parameter names and values. In this query-string format, spaces are written as +; a literal plus sign is encoded as %2B. By contrast, encodeURIComponent represents a space as %20. Both can represent data, but use the API for the URL component you are building instead of mixing serialization rules manually.
This also avoids common delimiter bugs. If a value contains & or =, setting it as a parameter keeps those characters inside the value instead of accidentally creating another parameter.
Keep raw input separate from encoded text
Encoding is a serialization step. If the input is the literal text %2F, passing it to encodeURIComponent produces %252F: the percent sign is encoded as %25. That is correct when %2F is what the user actually typed.
Do not try to guess whether arbitrary input is “already encoded.” Keep raw values as raw values, and encode them when constructing the URL. If an application has a documented interface that supplies encoded values, decode them only at that defined boundary and handle malformed sequences; do not repeatedly decode until the text looks readable. Repeated decoding can turn data into URL delimiters.
In JavaScript, decodeURIComponent can throw a URIError for malformed percent escapes or invalid UTF-8 sequences. Handle that error if you explicitly decode input. Also note that encodeURIComponent can throw for an unpaired UTF-16 surrogate; validate or normalize input according to your application’s Unicode requirements rather than silently assuming every JavaScript string can be encoded.
Encoding is not validation or authorization
Percent encoding makes characters representable in a URL component; it does not decide whether the value is acceptable. Validate user input against the rules for the field—for example, an identifier’s permitted characters, a maximum length, or an allowed sort order. Apply authorization on the server for every protected resource.
Do not put secrets in paths or query strings. URLs can be captured in browser history, server and proxy logs, analytics, and other places. If a value is sensitive, use an appropriate request body or another suitable mechanism, and still apply access controls.
Common Mistakes
- Putting raw input into a URL template: A
?,#, or&in the value may change the URL’s structure. Encode the path segment or set a query parameter through the URL API. - Encoding the whole path: This can encode intended
/separators. Encode only the dynamic segment. - Using
encodeURIfor a path segment: It preserves more URL delimiters thanencodeURIComponent, including characters that can change the structure of a value inserted into a segment. - Encoding twice: Passing previously serialized text back through an encoder changes percent escapes, such as
%2Fto%252F. - Treating
%2Fas a universal slash barrier: A downstream component may decode or reject encoded slashes. Test the application’s actual router and infrastructure. - Treating encoding as input validation: Encoded malicious or invalid data is still data the server must validate and authorize.
Best Practices
- Decide what the value means before deciding where it goes: resource identity usually belongs in the path; request options usually belong in the query.
- Use
new URL()to construct URLs,encodeURIComponent()for a raw dynamic path segment, andURLSearchParamsfor query parameters. - Store and pass raw values internally. Serialize once when constructing the URL; decode only at a clearly defined boundary.
- Define server-side validation, canonicalization, and authorization rules for the value. Test dots, slashes, percent signs, Unicode, and malformed escapes across the full request path.
- Avoid placing credentials, tokens, or other sensitive values in URLs.
Conclusion
Choose a path segment for a resource identifier and a query parameter for request options. Encode each value according to its URL component, keep raw input distinct from encoded output, and rely on server-side validation and authorization—not encoding—for security.
Research note: Live standards and platform documentation could not be retrieved for this draft. The examples describe established JavaScript URL behavior, but verify framework- and server-specific decoding behavior against the documentation for your deployed stack before relying on it.
