How to URL-Encode a Query String in Python
URL encoding (also called percent-encoding) converts special characters in a query string into a format safe for transmission over the internet. Characters like spaces, ampersands, and question marks are replaced with their percent-encoded equivalents (e.g., space becomes %20 or +). This is essential when constructing URLs with dynamic parameters, sending form data, or building API requests. This guide covers multiple methods to URL-encode query strings in Python.
Why URL Encoding Is Necessary
URLs can only contain a limited set of characters from the ASCII character set. Special characters like spaces, &, =, ?, and non-ASCII characters must be encoded to avoid breaking the URL structure:
Without encoding: https://example.com/search?q=hello world&lang=python
With encoding: https://example.com/search?q=hello+world&lang=python
Without encoding, the space in "hello world" would break the URL, and the & could be misinterpreted as a parameter separator.
Method 1: Using urllib.parse.urlencode() (Recommended)
The urllib.parse.urlencode() function is the standard library solution for encoding dictionaries into properly formatted query strings:
from urllib.parse import urlencode
params = {
"query": "python programming",
"page": "1",
"sort": "relevance",
"filter": "2024 & beyond"
}
encoded = urlencode(params)
print(encoded)
Output:
query=python+programming&page=1&sort=relevance&filter=2024+%26+beyond
Spaces are encoded as +, the & inside the value is encoded as %26, and key-value pairs are joined with &.
Building a Complete URL
from urllib.parse import urlencode
base_url = "https://api.example.com/search"
params = {
"q": "machine learning",
"category": "articles",
"limit": "10"
}
full_url = f"{base_url}?{urlencode(params)}"
print(full_url)
Output:
https://api.example.com/search?q=machine+learning&category=articles&limit=10
Handling Multiple Values for the Same Key
Use the doseq=True parameter when a key has multiple values:
from urllib.parse import urlencode
params = {
"color": ["red", "blue", "green"],
"size": "large"
}
encoded = urlencode(params, doseq=True)
print(encoded)
Output:
color=red&color=blue&color=green&size=large
Without doseq=True, the list would be encoded as a string representation:
from urllib.parse import urlencode
# WRONG: without doseq, lists are converted to their string representation
encoded_wrong = urlencode(params)
print(encoded_wrong)
Output:
color=%5B%27red%27%2C+%27blue%27%2C+%27green%27%5D&size=large
Always pass doseq=True when your parameters contain lists or tuples as values. Without it, the list object itself is converted to a string and then encoded, producing incorrect results.
Using quote_via for Percent-Encoded Spaces
By default, urlencode() encodes spaces as +. To use %20 instead (as required by some APIs), pass quote_via:
from urllib.parse import urlencode, quote
params = {"query": "hello world", "page": "1"}
# Default: spaces as +
print(urlencode(params))
# Spaces as %20
print(urlencode(params, quote_via=quote))
Output:
query=hello+world&page=1
query=hello%20world&page=1
Method 2: Using urllib.parse.quote() and quote_plus()
For encoding individual strings rather than entire dictionaries, use quote() or quote_plus():
from urllib.parse import quote, quote_plus
text = "hello world & goodbye!"
# quote_plus: spaces → +, suitable for query string values
print("quote_plus:", quote_plus(text))
# quote: spaces → %20, suitable for URL path segments
print("quote: ", quote(text))
Output:
quote_plus: hello+world+%26+goodbye%21
quote: hello%20world%20%26%20goodbye%21
| Function | Spaces Become | Best For |
|---|---|---|
quote_plus() | + | Query string values (?key=value) |
quote() | %20 | URL path segments (/path/to/resource) |
Specifying Safe Characters
Both functions accept a safe parameter to specify characters that should not be encoded:
from urllib.parse import quote
path = "/api/v2/search results/page 1"
# Without safe: slashes are encoded
print(quote(path))
# With safe='/': slashes are preserved
print(quote(path, safe='/'))
Output:
/api/v2/search%20results/page%201
/api/v2/search%20results/page%201
Method 3: Building Custom Query Strings Manually
For more control over the encoding process, manually encode each key-value pair:
from urllib.parse import quote_plus
params = {
"name": "John Doe",
"city": "New York",
"interest": "AI & ML"
}
encoded = '&'.join(
f"{quote_plus(key)}={quote_plus(value)}"
for key, value in params.items()
)
print(encoded)
Output:
name=John+Doe&city=New+York&interest=AI+%26+ML
This produces the same result as urlencode() but gives you the flexibility to customize encoding for individual keys or values.
For most cases, urllib.parse.urlencode() is the best choice - it handles dictionary encoding, proper escaping, and parameter joining automatically. Use quote() or quote_plus() only when you need to encode individual strings.
Decoding URL-Encoded Strings
To reverse the encoding, use unquote() or unquote_plus():
from urllib.parse import unquote, unquote_plus, parse_qs
# Decode individual strings
encoded = "hello+world+%26+goodbye%21"
print("Decoded:", unquote_plus(encoded))
# Parse a full query string back into a dictionary
query_string = "name=John+Doe&city=New+York&interest=AI+%26+ML"
parsed = parse_qs(query_string)
print("Parsed:", parsed)
Output:
Decoded: hello world & goodbye!
Parsed: {'name': ['John Doe'], 'city': ['New York'], 'interest': ['AI & ML']}
parse_qs() returns lists for each value because query strings can have multiple values per key. Use parse_qs(query_string)['key'][0] to get a single value, or use parse_qs(query_string, keep_blank_values=True) to preserve empty values.
Common Mistake: Double Encoding
A frequent error is encoding a string that has already been encoded, resulting in % signs being encoded again:
from urllib.parse import quote_plus
original = "hello world"
encoded_once = quote_plus(original)
print("Encoded once:", encoded_once)
# WRONG: encoding an already-encoded string
encoded_twice = quote_plus(encoded_once)
print("Encoded twice:", encoded_twice)
Output:
Encoded once: hello+world
Encoded twice: hello%2Bworld
The + from the first encoding gets encoded to %2B, producing an incorrect result.
The correct approach**
Encode only once, using the original unencoded string:
from urllib.parse import quote_plus
original = "hello world"
encoded = quote_plus(original)
print("Correctly encoded:", encoded)
Never URL-encode a string that has already been encoded. Double encoding corrupts the data and produces incorrect values when decoded. Always work with the original, unencoded string.
Quick Reference
| Task | Function | Example Output |
|---|---|---|
| Encode a dictionary | urlencode(params) | key1=val1&key2=val2 |
| Encode with lists | urlencode(params, doseq=True) | k=v1&k=v2 |
| Encode a single string (query) | quote_plus(text) | hello+world |
| Encode a single string (path) | quote(text) | hello%20world |
| Decode a query string | unquote_plus(text) | hello world |
| Parse query string to dict | parse_qs(query) | {'key': ['value']} |
The urllib.parse module provides all the tools needed for URL encoding in Python. Use urlencode() for dictionaries, quote_plus() for individual query values, and quote() for URL path components.