Skip to main content

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.

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
warning

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
FunctionSpaces BecomeBest For
quote_plus()+Query string values (?key=value)
quote()%20URL 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.

tip

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']}
info

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)
danger

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

TaskFunctionExample Output
Encode a dictionaryurlencode(params)key1=val1&key2=val2
Encode with listsurlencode(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 stringunquote_plus(text)hello world
Parse query string to dictparse_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.