Axonix Tools
That Time a Single & Broke Production: URL Encoding Explained
Back to Insights
URL EncodingURL DecodingWeb Development

That Time a Single & Broke Production: URL Encoding Explained

8 min read
Reviewed:

URL encoding exists for a reason. Here's what it actually does, why you keep getting 400 errors, and a quick tool to fix it.

The three-hour debugging session I don't talk about

  1. Production environment. API calls were failing silently. The error message was "Invalid request," which is the technical equivalent of a shrug.

I checked the payload. Fine. Checked the headers. Fine. Checked authentication. Fine. Rebuilt the entire request from scratch. Still broken.

Three hours later, I found it. One & character in a user-submitted search query. Unencoded.

The user had searched for "bread & butter." The server read that as two separate parameters: bread and butter with no value. The & is the standard separator for query parameters, so the server thought I was sending it a malformed request.

One character. Three hours of my life. I still think about this sometimes when I'm trying to fall asleep.

Why URLs can't handle normal text

URLs were designed in an era when the internet was built by people who agreed on things through email threads and RFC documents. The result is a system that works well but is incredibly picky about what characters it accepts.

Spaces don't work in URLs. Ampersands are reserved for separating parameters. Question marks start the query string. Hash symbols mark the fragment identifier. Forward slashes separate path segments. Equals signs separate parameter names from values.

All of these characters have special meaning in a URL. If you want to use one of them as actual data, you need to encode it.

The encoding scheme is called percent-encoding. You take the character, look up its ASCII value in hexadecimal, and prefix it with a percent sign. A space becomes %20. An ampersand becomes %26. A question mark becomes %3F.

Normal:  cats & dogs
Encoded: cats%20%26%20dogs

When the browser or server sees %26, it knows to treat it as a literal ampersand character, not as a parameter separator.

The characters that cause the most trouble

Here are the ones I've been burned by:

| Character | Encoded | Why it breaks things | |-----------|---------|---------------------| | Space | %20 or + | URLs can't contain literal spaces | | & | %26 | Separates query parameters | | ? | %3F | Starts the query string | | = | %3D | Separates parameter names from values | | # | %23 | Marks the fragment identifier | | / | %2F | Separates path segments | | + | %2B | Means "space" in form encoding | | % | %25 | The encoding character itself |

The plus sign is the sneaky one. In application/x-www-form-urlencoded encoding, a + means a space. So if your data contains an actual plus sign and you don't encode it, it gets interpreted as a space. I've seen this break API keys, mathematical expressions, and phone numbers.

When URL encoding will bite you

API calls with user input

If someone searches for "Tom & Jerry" and you stick that directly into a URL without encoding it, the server sees Tom as one parameter and Jerry as another. Encode the search term first.

Redirect URLs

Ever seen a URL with a ?return_to= parameter that contains another full URL? That inner URL needs to be encoded, because it contains question marks, ampersands, and slashes that would otherwise be interpreted as part of the outer URL.

Normal redirect:
https://example.com/login?return_to=https://example.com/dashboard?page=2&tab=settings

This breaks because the server sees three parameters:
- return_to = https://example.com/dashboard?page=2
- tab = settings

Encoded redirect:
https://example.com/login?return_to=https%3A%2F%2Fexample.com%2Fdashboard%3Fpage%3D2%26tab%3Dsettings

Now the server sees one parameter:
- return_to = https://example.com/dashboard?page=2&tab=settings

Form data

When you submit a form with application/x-www-form-urlencoded, the browser encodes the data automatically. But if you're building HTTP requests manually with fetch or axios, encoding is your responsibility.

Sharing links with special characters

Someone sends you a Google Drive link with a filename that contains spaces or special characters. If the link isn't properly encoded, it won't open. This happens constantly with shared documents.

How encoding works in JavaScript

If you're writing JavaScript, you have two built-in functions for URL encoding:

encodeURIComponent()

This is the one you want most of the time. It encodes everything except letters, numbers, and -_.!~*'().

const search = "bread & butter";
const encoded = encodeURIComponent(search);
// Result: "bread%20%26%20butter"

const url = `https://api.example.com/search?q=${encoded}`;
// Result: "https://api.example.com/search?q=bread%20%26%20butter"

encodeURI()

This one is less aggressive. It only encodes characters that are invalid in a URL, but leaves alone characters that have special meaning within a URL, like &, =, ?, and #.

const url = "https://example.com/search?q=bread & butter";
const encoded = encodeURI(url);
// Result: "https://example.com/search?q=bread%20&%20butter"

Notice that the & is not encoded. That's because encodeURI() assumes you're encoding a complete URL and the & is meant to be a parameter separator. If you're encoding a value that goes inside a URL, use encodeURIComponent() instead.

The decoding side

const encoded = "bread%20%26%20butter";
const decoded = decodeURIComponent(encoded);
// Result: "bread & butter"

The double-encoding trap

Here's a mistake I've made more than once. You encode a value, then encode it again by accident.

const value = "bread & butter";
const once = encodeURIComponent(value);
// "bread%20%26%20butter"

const twice = encodeURIComponent(once);
// "bread%2520%2526%2520butter"

The second encoding turns the % signs into %25. Now when the server decodes it once, it gets bread%20%26%20butter instead of bread & butter. The data is still wrong.

This happens when you encode a value that's already been encoded, or when a library encodes something for you and then you encode it again. If your decoded output still looks encoded, you probably double-encoded it somewhere.

URL encoding in different languages

Python

from urllib.parse import quote, unquote

encoded = quote("bread & butter")
# 'bread%20%26%20butter'

decoded = unquote(encoded)
# 'bread & butter'

Node.js

const querystring = require('querystring');

const encoded = querystring.escape("bread & butter");
// 'bread%20%26%20butter'

const decoded = querystring.unescape(encoded);
// 'bread & butter'

PHP

$encoded = urlencode("bread & butter");
// 'bread+%26+butter'

$decoded = urldecode($encoded);
// 'bread & butter'

Note that PHP's urlencode() uses + for spaces instead of %20. This is valid in form encoding but can cause issues in other contexts. Use rawurlencode() if you need %20 for spaces.

Debugging URL encoding issues

Getting unexpected 400 errors on API calls? Before you start adding console.log statements everywhere:

  1. Copy the full URL from your browser's address bar or your network tab.
  2. Paste it into a URL decoder.
  3. Look at the decoded output and check if any parameters look broken or truncated.

Half the time it's an encoding issue. The other half, well, good luck.

The URL Encoder/Decoder runs entirely in your browser. Paste text in, get encoded text out. Paste encoded text in, get normal text out. No signup, no server, no data leaving your machine.

When to encode everything versus encoding selectively

There are two schools of thought here.

Encode everything that isn't a letter, number, or one of -_.~. This is the safe approach. You won't miss anything. The downside is that your URLs look like gibberish.

Encode only the characters that have special meaning in URLs. This keeps URLs more readable. The downside is that you might miss an edge case.

I lean toward encoding everything. The readability cost is minimal and the safety benefit is real. If a character doesn't need to be unencoded, encode it.

Common mistakes

Forgetting to encode before concatenating. If you build a URL by string concatenation, encode each value before you insert it:

// Wrong
const url = `https://api.example.com/search?q=${searchTerm}`;

// Right
const url = `https://api.example.com/search?q=${encodeURIComponent(searchTerm)}`;

Encoding the entire URL instead of individual values. encodeURI() on a full URL won't encode the & and = characters that separate parameters. Use encodeURIComponent() on each value individually.

Assuming the browser will encode for you. Modern browsers do encode URLs in the address bar, but they don't encode URLs in your JavaScript code. If you're building URLs programmatically, you're responsible for encoding.

Not encoding redirect URLs. If you're passing a URL as a parameter value, encode the entire inner URL, not just parts of it.

Frequently asked questions

What's the difference between encodeURI() and encodeURIComponent()?

encodeURI() encodes a complete URL. It leaves alone characters that have special meaning within URLs, like &, =, ?, and #. encodeURIComponent() encodes everything except letters, numbers, and -_.!~*'(). Use encodeURIComponent() when encoding individual values that go inside a URL.

Why does my encoded URL look different in the browser's address bar?

Browsers often re-encode or decode URLs for display purposes. The address bar might show a decoded version of the URL even though the actual request uses the encoded version. Check the Network tab in your browser's developer tools to see the actual URL being sent.

Do I need to encode URLs in HTML links?

If you're writing a static <a href="..."> tag, the browser handles encoding automatically in most cases. If you're generating URLs dynamically with JavaScript, encode them yourself.

What about non-ASCII characters like emojis or accented letters?

These need to be encoded too. An emoji like 🔥 becomes %F0%9F%94%A5. An accented character like é becomes %C3%A9. encodeURIComponent() handles these correctly.

Is URL encoding the same as HTML encoding?

No. URL encoding uses percent signs (%20 for space). HTML encoding uses entity references (&nbsp; for space). They serve different purposes. URL encoding is for URLs. HTML encoding is for HTML content. Don't mix them up.

Final note

URL encoding is one of those things that seems trivial until it breaks your application at 2am. The rules are simple: encode any character that has special meaning in a URL before you use it as data. Use encodeURIComponent() in JavaScript. Decode on the server side.

If you need to encode or decode something quickly, the URL Encoder/Decoder is right here. Paste, click, done.

Written by Axonix Team

Axonix Team - Technical Writer @ Axonix

Share this article

Discover More

View all articles

Need a tool for this workflow?

Axonix provides 100+ browser-based tools for practical development, design, file, and productivity tasks.

Explore Our Tools