Skip to content
T
Tools.Town
Free Online Tools for Everyone
Developer Tools

URL Encoding Explained: When, Why & How to Percent-Encode

A developer's guide to URL encoding — what characters need encoding, how percent-encoding works in practice, common mistakes, and how to encode query strings correctly.

25 June 2026 4 min read By Tools.Town Team Fact Checked

Key Takeaways

  • A URL is a sequence of characters from a defined safe set
  • It percent-encodes every character except the unreserved set: A–Z, a–z, 0–9, -, _,
  • No

Why URLs need encoding

A URL is not just text — it is a structured identifier with specific delimiters (://, /, ?, &, =, #) that have precise meanings. If you want to pass data that contains any of these characters, or any character outside the basic ASCII set, you must encode it so the URL parser cannot mistake your data for a structural delimiter.

The encoding format is defined by RFC 3986 (the authoritative URI standard): replace each unsafe character with % followed by its hexadecimal byte value in UTF-8. The result is called a percent-encoded string, and the process is called percent-encoding or URL encoding.

The Query String Builder handles this encoding for you — you paste in plain-text key-value pairs and get back a correctly encoded query string. This guide explains the rules it follows, so you understand why the output looks the way it does.

Safe vs unsafe characters

RFC 3986 defines three categories:

Unreserved characters — never need encoding These are always safe anywhere in a URI: A–Z, a–z, 0–9, and the four symbols -, _, ., ~. A well-written encoder will not touch these.

Reserved characters — must be encoded when used as data These characters have structural meaning in a URL: :, /, ?, #, [, ], @, !, $, &, ', (, ), *, +, ,, ;, =. When you want to include them as literal data (not as delimiters), they must be percent-encoded.

Other characters — always need encoding Spaces, Unicode characters (accented letters, emoji, non-Latin scripts), control characters, and anything else not in the two groups above must always be percent-encoded.

How percent-encoding works step by step

Take a space character:

  1. Its Unicode code point is U+0020.
  2. In UTF-8 it is a single byte: 0x20.
  3. The percent-encoded form is %20.

Take the euro sign :

  1. Its Unicode code point is U+20AC.
  2. In UTF-8 it is three bytes: 0xE2 0x82 0xAC.
  3. The percent-encoded form is %E2%82%AC.

Take the letter é (e with acute):

  1. U+00E9.
  2. Two UTF-8 bytes: 0xC3 0xA9.
  3. Percent-encoded: %C3%A9.

Everything else follows the same pattern: take the UTF-8 bytes of the character and write % before each byte.

Query strings in particular

When building a query string, you encode the keys and values separately — not the whole URL, and not the ?, &, and = delimiters. Those remain literal:

https://example.com/search?q=hello%20world&city=S%C3%A3o%20Paulo
                           │─────────────│ │─────────────────────│
                           key=value        key=value (encoded separately)

The Query String Builder does exactly this: it percent-encodes each key and value individually and joins them with the literal &.

The + space problem

There are two conventions for encoding a space in a query string:

RFC 3986: encode as %20. This is the standard for URLs in general and for API clients.

application/x-www-form-urlencoded: encode as +. This is what browsers use when submitting an HTML <form method="GET">. The server is expected to decode + back to a space.

The two conventions conflict: if your data contains a literal +, it must be encoded as %2B under form encoding to distinguish it from a space. Under RFC 3986, a literal + does not need encoding (it is in the reserved set but not a structural delimiter in query strings), but it is safest to encode it as %2B in either case.

Practical rule: use RFC 3986 (%20) for new code. Use form encoding only when explicitly required by a system that expects it. The Query String Builder lets you switch between them.

Common mistakes

Encoding the full URL instead of just the values

// WRONG — encodes the : and // in the scheme
encodeURIComponent('https://example.com/search?q=hello world');
// → 'https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dhello%20world'

// RIGHT — encode only the value
'https://example.com/search?q=' + encodeURIComponent('hello world');
// → 'https://example.com/search?q=hello%20world'

Double-encoding

// If the value is already encoded, encoding again produces %25xx
encodeURIComponent('hello%20world');
// → 'hello%2520world' — %25 is the encoding of %

The Query String Builder’s parse mode reveals double-encoding: you will see %25 in the raw column alongside the original %20 in the decoded column.

Not encoding Unicode

ASCII-only test data masks encoding bugs. The real issue surfaces when a user enters a name like André, a city like São Paulo, or a search term in Japanese or Arabic. Always test with Unicode values in both directions.

Encoding structural characters that should stay literal

// WRONG — encodes the whole path
fetch(encodeURIComponent('/api/search?q=hello'));

// RIGHT — use the URL constructor or encode only the query value
const url = new URL('/api/search', 'https://example.com');
url.searchParams.set('q', 'hello world');
// url.href → 'https://example.com/api/search?q=hello+world'
// Note: URLSearchParams uses form-encoding (+ for spaces)

How different languages handle it

JavaScript (browser/Node.js)

URLSearchParams is the idiomatic choice — it handles encoding automatically:

const p = new URLSearchParams({ q: 'hello world', tag: 'C# dev' });
p.toString(); // q=hello+world&tag=C%23+dev  (form-encoded)

For RFC-3986 encoding, use encodeURIComponent on each value manually:

const qs = Object.entries({ q: 'hello world' })
  .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
  .join('&');
// q=hello%20world

Python

from urllib.parse import urlencode, quote
urlencode({'q': 'hello world'})       # q=hello+world (form)
quote('hello world', safe='')         # hello%20world (RFC-3986)

Go

u := url.Values{}
u.Set("q", "hello world")
u.Encode()  // q=hello+world (form)

Decoding

To reverse the encoding, replace each %xx sequence with the corresponding byte and decode the resulting byte sequence as UTF-8. In JavaScript: decodeURIComponent('hello%20world')hello world. Watch out for malformed sequences (e.g. a bare % not followed by two hex digits) — robust decoders catch the error and leave the sequence unchanged rather than throwing.

Summary

Percent-encoding keeps URLs unambiguous by replacing unsafe characters with %xx sequences. The unreserved characters (ASCII letters, digits, -_. ~) never need encoding. Reserved characters need encoding when used as data. Spaces become %20 (RFC-3986) or + (form encoding). Encode keys and values individually, not the whole URL. To build or decode a query string quickly and correctly, use the Query String Builder.

Advertisement

Try Query String Builder — Free

Apply what you just learned with our free tool. No sign-up required.

Try Query String Builder

Frequently Asked Questions

Why can't I put a space in a URL?
A URL is a sequence of characters from a defined safe set. Space is not in that set — it would make the URL ambiguous (where does the URL end?). Spaces must be encoded as %20 or, in form encoding, as +.
What does encodeURIComponent do in JavaScript?
It percent-encodes every character except the unreserved set: A–Z, a–z, 0–9, -, _, ., ~. This makes it safe for encoding a single query string value. Do not use it on a full URL — it will encode the : and // in https:// and break the URL.
Is URL encoding the same as HTML encoding?
No. HTML encoding replaces characters with HTML entities (& becomes &amp;, < becomes &lt;). URL encoding replaces characters with %hex sequences. They serve different purposes and must not be mixed up.

Was this guide helpful?

Your feedback helps us improve our content.

Get the best Developer Tools tips & guides in your inbox

Join 25,000+ users who get our weekly developer tools insights.