URL Encoding Explained: A Practical Guide
Learn how percent-encoding works, when to use encodeURIComponent, why spaces become %20 or +, and how to prevent broken links and double encoding.
On this page
- Quick answer
- Start with the anatomy of a URL
- What percent-encoding actually does
- Reserved and unreserved characters
- Space as %20 versus +
- encodeURI versus encodeURIComponent
- The safer way to build query strings
- A practical workflow for encoding a link
- 1. Separate structure from data
- 2. Choose the API for the component
- 3. Encode once
- 4. Parse and inspect the result
- 5. Test the destination
- The double-encoding trap
- Decoding and malformed input
- Fragments, campaign links, and QR codes
- Security and privacy boundaries
- Common mistakes and fixes
- A compact debugging checklist
- Frequently Asked Questions
- The reliable rule
A URL can break even when every word in it looks correct. Put coffee & cream into a query value without encoding the ampersand, and the server may read cream as a second parameter. Encode an already encoded value, and %20 becomes %2520. Treat a literal plus sign like a space, and a search for C++ quietly becomes C followed by two spaces.
These failures all come from the same misunderstanding: a URL is not one undifferentiated string. It is a set of components, and characters such as /, ?, &, =, and # have structural jobs. URL encoding lets those same characters travel as data without being mistaken for separators.
This guide explains percent-encoding from first principles, shows the practical difference between encodeURI, encodeURIComponent, and URLSearchParams, and gives you a repeatable way to build and debug links without guessing.
Quick answer
URL encoding, usually called percent-encoding, represents a byte as a percent sign followed by two hexadecimal digits. A space can be written as %20; an ampersand used as data becomes %26. Non-ASCII text is first represented as UTF-8 bytes, so one visible character may produce several %HH sequences.
Encode values before inserting them into a URL component. Do not encode the entire finished URL with a component encoder, and do not encode a value that is already encoded. In browser JavaScript, prefer the URL and URLSearchParams APIs for complete links. Use encodeURIComponent when you specifically need to encode one component value.
The URL Encoder / Decoder uses encodeURIComponent and decodeURIComponent, making it useful for inspecting one value or reproducing an encoding problem without writing code.
Try it right here
URL Encoder / Decoder
Start with the anatomy of a URL
Consider this address:
https://example.com/products/coffee?q=dark roast&sort=price#reviews| Component | Example | Purpose |
|---|---|---|
| Scheme | https | How the resource is accessed |
| Host | example.com | The server name |
| Path | /products/coffee | The resource hierarchy |
| Query | q=dark roast&sort=price | Named parameters for the resource |
| Fragment | reviews | A location or state within the resulting document |
The punctuation between these components is meaningful. The first ? starts the query, & separates parameter pairs, = separates a name from its value, and # starts the fragment. If one of those characters belongs inside a value, it usually needs encoding so the parser does not assign it a structural role.
The URL Query Parser exposes the base URL, fragment, and each query parameter as separate fields. That makes it easier to see whether a suspicious ampersand is a delimiter or part of a value.
What percent-encoding actually does
RFC 3986, the generic URI syntax specification, defines a percent-encoded octet as % followed by two hexadecimal digits. The mechanism works at the byte level, not directly at the level of visible characters.
For ASCII characters, the relationship is easy to see:
| Character | ASCII byte | Percent-encoded form |
|---|---|---|
| Space | 20 | %20 |
& | 26 | %26 |
= | 3D | %3D |
# | 23 | %23 |
% | 25 | %25 |
Unicode characters are encoded as UTF-8 first. The word café becomes caf%C3%A9 because é is represented by the two UTF-8 bytes C3 A9. A check mark becomes %E2%9C%93, three sequences for three UTF-8 bytes. This is expected; the number of encoded triplets does not need to match the number of visible characters.
Hexadecimal letters are case-insensitive in encoded URLs, so %2f and %2F represent the same byte. RFC 3986 recommends uppercase hexadecimal for consistency, which is what common browser APIs produce.
Reserved and unreserved characters
RFC 3986 separates URL characters into two groups that matter here.
Unreserved characters are letters, digits, hyphen, period, underscore, and tilde. They can normally appear as data without encoding:
A-Z a-z 0-9 - . _ ~Reserved characters may act as delimiters:
: / ? # [ ] @ ! $ & ' ( ) * + , ; =Reserved does not mean forbidden. It means context matters. A slash should remain a slash when it separates path segments, but a slash that belongs inside a single path value may need to become %2F. An ampersand should remain unencoded between query parameters, but an ampersand inside a search phrase should become %26.
That is why “encode the URL” is incomplete advice. You need to know which component you are building and whether each reserved character is syntax or data.
Space as %20 versus +
Spaces have two common serialized forms:
- General percent-encoding uses
%20. - HTML form-style query serialization uses
+.
JavaScript's encodeURIComponent("dark roast") returns dark%20roast. By contrast, URLSearchParams serializes the same query value as dark+roast. Both commonly represent a space in query data, but the plus convention is specific to form-style query parsing; a plus is not a universal substitute for a space everywhere in a URL.
This distinction matters for literal plus signs. If the intended value is C++, a query serializer should produce C%2B%2B. Constructing a query by concatenating raw text can cause a parser to interpret those plus signs as spaces.
encodeURI versus encodeURIComponent
The names are frustratingly similar, but the intended inputs differ.
| Function | Intended input | Preserves URL delimiters? | Typical use |
|---|---|---|---|
encodeURI | A mostly complete URI | Yes | Encoding spaces and Unicode in a complete URL while keeping its structure |
encodeURIComponent | One component value | No; encodes characters such as ?, &, =, and / | Encoding a query value or one dynamic path segment |
URLSearchParams | Query parameter names and values | Builds the delimiters itself | Creating or editing a query string |
MDN's encodeURIComponent reference notes that it encodes more characters than encodeURI, including characters that participate in URL syntax. Compare these two calls:
const value = "coffee & cream";
console.log(encodeURI(value));
// coffee%20&%20cream
console.log(encodeURIComponent(value));
// coffee%20%26%20creamThe first result leaves & untouched. That is unsafe if the result will become a query value because the ampersand may start another parameter. The component function encodes it as data.
Now consider a complete URL:
const address = "https://example.com/search?q=dark roast#top";
console.log(encodeURI(address));
// https://example.com/search?q=dark%20roast#top
console.log(encodeURIComponent(address));
// https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Ddark%20roast%23topThe second output is valid encoded text, but it is no longer a navigable URL because every structural delimiter was encoded. It is suitable only if the entire address itself is being stored as a value inside another component.
The safer way to build query strings
String concatenation makes it easy to forget an encoder, add a second question mark, or place parameters after a fragment. The browser URL APIs keep structure and data separate.
const url = new URL("https://example.com/search");
url.searchParams.set("q", "coffee & cream");
url.searchParams.set("language", "C++");
url.searchParams.append("tag", "café");
console.log(url.href);
// https://example.com/search?q=coffee+%26+cream&language=C%2B%2B&tag=caf%C3%A9The code never manually adds ?, &, or =. It supplies decoded values, and the serializer adds the right delimiters and encoding. MDN's URLSearchParams documentation also covers repeated keys: use append for multiple values and getAll when reading them.
Repeated parameters are legitimate:
const params = new URLSearchParams();
params.append("color", "blue");
params.append("color", "green");
console.log(params.toString());
// color=blue&color=green
console.log(params.getAll("color"));
// ["blue", "green"]Do not replace append with set unless you intend to collapse repeated values into one. Servers and frameworks disagree on how they expose duplicate keys, so confirm the destination's behavior.
A practical workflow for encoding a link
1. Separate structure from data
Write down the fixed base URL, path segments, query keys, query values, and fragment. If a user supplies a value, treat it as data rather than part of the URL syntax.
2. Choose the API for the component
Use URL for the overall address, URLSearchParams for query names and values, and encodeURIComponent for an isolated component when you cannot use the structured APIs. Avoid the obsolete JavaScript escape function; it does not implement modern URL encoding rules.
3. Encode once
Pass raw, decoded data to the encoder. If the input already contains sequences such as %20, determine whether they are real encoding or literal text before proceeding.
4. Parse and inspect the result
Use the URL Query Parser to verify the base, hash, and parameter values. A correct-looking address can still contain an unintended extra parameter or a fragment in the wrong place.
5. Test the destination
Open the link in the environment where it will be used. Redirects, proxies, application routers, and server frameworks can normalize URLs differently. Confirm both the destination page and the values received by the application.
The double-encoding trap
Double encoding happens when encoded text is passed through an encoder again:
const once = encodeURIComponent("hello world");
console.log(once);
// hello%20world
const twice = encodeURIComponent(once);
console.log(twice);
// hello%2520worldThe second pass encodes the percent sign as %25. One decoding pass now returns hello%20world, not hello world. The OWASP explanation of double encoding describes how inconsistent decoding layers can also become a security problem when filters and application code interpret the same input differently.
Common causes include:
- Encoding a value in the client and again on the server.
- Passing an encoded string into
URLSearchParams.append, which expects decoded data. - Encoding a complete URL before placing it into a library that encodes automatically.
- Decoding at a proxy and decoding again in application code.
The fix is ownership: decide which layer encodes and which layer decodes. Make that contract explicit, then test with a value containing a space, percent sign, plus sign, ampersand, slash, and non-ASCII character.
Decoding and malformed input
decodeURIComponent reverses component encoding, but it is deliberately strict. A percent sign not followed by two hexadecimal digits, or a byte sequence that is not valid UTF-8, causes a URIError.
function safelyDecode(value) {
try {
return { ok: true, value: decodeURIComponent(value) };
} catch {
return { ok: false, value };
}
}
console.log(safelyDecode("coffee%20shop"));
// { ok: true, value: "coffee shop" }
console.log(safelyDecode("save%now"));
// { ok: false, value: "save%now" }Do not “repair” malformed input by repeatedly decoding until no percent signs remain. A percent sign may be legitimate data, and repeated decoding changes meaning. Reject the value, preserve it for inspection, or apply a narrowly defined recovery rule at the boundary where the data enters your system.
Fragments, campaign links, and QR codes
A fragment belongs at the end of a URL, after the query:
https://example.com/guide?source=newsletter#examplesIf parameters are appended after #examples, they become part of the fragment rather than the query. This is a common failure in hand-built campaign links. The UTM Builder correctly encodes parameter values, while the guide to UTM parameters explains how to name those values consistently. When a destination includes a fragment, assemble the URL with the URL API or verify the finished structure before distributing it.
Encoding also matters before you put a long link into a QR code. A QR generator faithfully stores the text you provide; it does not know whether an unencoded ampersand broke the destination. Test the final URL first, then create it with the QR Code Generator. The companion article on how QR codes work covers scan testing and static versus redirect-based codes.
Security and privacy boundaries
Encoding is not encryption, validation, or sanitization. Anyone who can see the URL can decode its percent sequences. Do not put passwords, access tokens, private personal data, or other secrets into a query string merely because the characters look obscured.
URLs can appear in browser history, copied messages, analytics systems, server logs, monitoring tools, and referrer data. OWASP's application security guidance recommends excluding sensitive information from URLs because browsers and servers may retain them.
On the server, parse URL components before decoding values. RFC 3986 warns that decoding too early can turn encoded data into a delimiter and change how the URL is interpreted. Then validate the decoded value according to its actual purpose. Encoding prevents delimiter collisions; it does not make an untrusted path, redirect target, database filter, or HTML value safe.
Common mistakes and fixes
| Mistake | Symptom | Fix |
|---|---|---|
Raw & inside a value | One value becomes two parameters | Encode the component or use URLSearchParams |
Encoding the whole URL with encodeURIComponent | The link no longer navigates | Encode values, or use URL for the complete address |
| Encoding twice | %20 becomes %2520 | Give one layer responsibility for encoding |
Treating every + as a literal plus | C++ becomes spaces | Serialize the value so plus signs become %2B |
| Decoding before splitting components | Encoded delimiters change structure | Parse first, decode component data second |
Adding a query after # | Server never receives the intended query | Put the query before the fragment |
| Using encoding to hide secrets | Sensitive data remains visible and retained | Keep secrets out of URLs |
| Ignoring repeated keys | Filters or selections disappear | Use append and verify server behavior |
A compact debugging checklist
When a URL works in one place but not another, check it in this order:
- Parse the URL into scheme, host, path, query, and fragment.
- List every query key and value, including duplicates.
- Look for raw
&,=,+,#, and stray percent signs inside values. - Search for
%25, which often signals a second encoding pass. - Compare the decoded value with the value the application expected.
- Test a non-ASCII value to confirm UTF-8 is handled consistently.
- Follow redirects and confirm the query survives each hop.
- Remove sensitive data before sharing the URL in a ticket or chat.
This sequence keeps you from randomly replacing characters until the link appears to work. It also produces a small, reproducible case that another developer can inspect.
Frequently Asked Questions
What is URL encoding?
Should I use encodeURI or encodeURIComponent?
encodeURIComponent for one dynamic value such as a query value or path segment. encodeURI preserves structural characters and is intended for a mostly complete URI. For building full URLs in browser JavaScript, URL and URLSearchParams are usually safer than either function alone.Why do spaces sometimes become %20 and sometimes +?
%20 is the general percent-encoded form of a space. Form-style query serialization, including URLSearchParams, uses + for spaces. A literal plus sign in query data must therefore be encoded as %2B.What does %25 mean in a URL?
%25 represents the percent sign itself. Seeing %2520 often indicates double encoding: the percent sign in an existing %20 sequence was encoded on a second pass.Can I decode an entire URL with decodeURIComponent?
Does URL encoding protect sensitive information?
Why does decodeURIComponent throw an error?
The reliable rule
Build a URL from components, keep syntax separate from data, and encode each value exactly once. Use structured browser APIs when possible, inspect the parsed result, and test the finished link through its real redirects and destination.
Once that rule is in place, percent signs stop being mysterious. They become what they were designed to be: a precise way to carry data through a syntax that already uses punctuation for structure.
Hands on
Tools mentioned in this article
URL Encoder / Decoder
Percent-encode URL components or decode percent-encoded text, with samples, swapping, copying, and text download.
URL Query Parser
Parse a URL into its base path, hash fragment, and editable query parameters, then rebuild and copy the result.
UTM Builder
Assemble campaign tracking URLs with utm_source, medium, campaign, term, and content parameters, correctly encoded for analytics.
QR Code Generator
Turn any link or text into a scannable QR code with adjustable size and error correction, then download it as a PNG or SVG.
More guides
Keep reading

What is JSON and how to format it correctly
A practical guide to JSON syntax, the mistakes that break it, and how a formatter turns a wall of text into something you can actually debug.

How to calculate EMI on any loan
The EMI formula explained in plain terms, a worked example, and what actually happens to your money over the life of a loan.

Password best practices in 2026
Why length beats complexity, how passphrases work, and a realistic system for managing passwords without losing your mind.
Put it into practice
Every guide comes with free tools to match.
No signup, no paywalls. Local tools run in your browser, and upload or AI steps are labeled clearly.