URL Encoding in Python — urllib.parse, requests, and FastAPI
Python's urllib.parse module provides quote, quote_plus, and urlencode for URL encoding. Here's how to encode URLs and query strings correctly in Python, requests library, and...
Use the tool
URL Encoder / Decoder
Percent-encode and decode URLs per RFC 3986.
Python’s urllib.parse module has everything needed for URL encoding. The key decision: use quote for path segments, quote_plus for query string values, and urlencode for multiple query parameters.
Use the URL Encoder to encode and decode URLs online.
urllib.parse functions
from urllib.parse import quote, quote_plus, urlencode, urljoin, urlparse
# quote: percent-encodes special characters (for paths)
quote('hello world') # 'hello%20world'
quote('fish & chips') # 'fish%20%26%20chips'
quote('/path/to/resource') # '/path/to/resource' (/ is safe by default)
# Encode / too:
quote('/path/to/file', safe='') # '%2Fpath%2Fto%2Ffile'
# quote_plus: encodes spaces as + (for query strings)
quote_plus('hello world') # 'hello+world'
quote_plus('fish & chips') # 'fish+%26+chips'
quote_plus('price=10') # 'price%3D10'
# urlencode: encode multiple query params as key=value pairs
params = {'q': 'fish & chips', 'format': 'json', 'page': 1}
urlencode(params) # 'q=fish+%26+chips&format=json&page=1'
# With doseq=True for lists:
params = {'tags': ['python', 'api', 'web']}
urlencode(params, doseq=True) # 'tags=python&tags=api&tags=web'
Building URLs correctly
from urllib.parse import urlencode, urljoin
# Build a URL with query parameters:
base = 'https://api.example.com/search'
params = {
'q': 'hello & world',
'format': 'json',
'page': 1,
'sort': 'date:desc',
}
url = f'{base}?{urlencode(params)}'
# 'https://api.example.com/search?q=hello+%26+world&format=json&page=1&sort=date%3Adesc'
# urljoin for relative URL resolution:
urljoin('https://example.com/docs/', 'api-reference')
# 'https://example.com/docs/api-reference'
urljoin('https://example.com/docs/intro', '../guide')
# 'https://example.com/docs/guide'
Decoding URLs
from urllib.parse import unquote, unquote_plus, parse_qs, parse_qsl
# Decode percent-encoded strings:
unquote('hello%20world') # 'hello world'
unquote('fish%20%26%20chips') # 'fish & chips'
# Decode + as space (query string style):
unquote_plus('hello+world') # 'hello world'
unquote_plus('hello%20world') # 'hello world' (works for both)
# Parse query string:
parse_qs('q=hello+world&format=json&tags=a&tags=b')
# {'q': ['hello world'], 'format': ['json'], 'tags': ['a', 'b']}
# parse_qsl preserves order and allows duplicates:
parse_qsl('a=1&b=2&a=3')
# [('a', '1'), ('b', '2'), ('a', '3')]
With the requests library
requests handles URL encoding automatically:
import requests
# Pass params as dict — requests encodes automatically:
response = requests.get(
'https://api.example.com/search',
params={
'q': 'fish & chips',
'format': 'json',
'page': 1,
}
)
# Actual URL: https://api.example.com/search?q=fish+%26+chips&format=json&page=1
# Inspect the prepared URL:
print(response.request.url)
# For POST with form-encoded body:
response = requests.post(
'https://example.com/login',
data={'username': 'alice', 'password': 'p@ssw0rd!'},
# Content-Type: application/x-www-form-urlencoded
)
# For POST with JSON body (not URL encoded):
response = requests.post(
'https://api.example.com/users',
json={'name': 'Alice', 'email': 'alice@example.com'},
# Content-Type: application/json
)
FastAPI URL parameters
FastAPI handles encoding/decoding automatically:
from fastapi import FastAPI, Query
from typing import Optional
app = FastAPI()
# Path parameters (decoded automatically):
@app.get('/items/{item_id}')
async def get_item(item_id: str):
return {'id': item_id}
# GET /items/hello%20world → item_id = "hello world"
# Query parameters (decoded automatically):
@app.get('/search')
async def search(
q: str = Query(...),
format: str = Query(default='json'),
page: int = Query(default=1, ge=1),
):
return {'q': q, 'format': format, 'page': page}
# GET /search?q=hello+world&page=2 → q = "hello world"
Encoding URL components manually
from urllib.parse import urlparse, urlunparse, urlencode
def build_url(scheme, host, path, params=None, fragment=None):
"""Build a properly encoded URL."""
query = urlencode(params) if params else ''
return urlunparse((scheme, host, path, '', query, fragment or ''))
build_url(
'https',
'api.example.com',
'/search',
params={'q': 'hello & world', 'page': 1}
)
# 'https://api.example.com/search?q=hello+%26+world&page=1'
Common mistakes
# WRONG: formatting directly into URL
user_input = "search term & more"
url = f"https://api.com/search?q={user_input}"
# 'https://api.com/search?q=search term & more' ← spaces and & not encoded!
# CORRECT: use urlencode or quote_plus
url = f"https://api.com/search?{urlencode({'q': user_input})}"
# 'https://api.com/search?q=search+term+%26+more'
# WRONG: using quote for query string values
url = f"https://api.com/search?q={quote(user_input)}"
# 'https://api.com/search?q=search%20term%20%26%20more' ← works but space should be +
# CORRECT for query values:
url = f"https://api.com/search?q={quote_plus(user_input)}"
Related tools
- URL Encoder — encode and decode URLs online
- URL Encoding Guide — what to encode and when
- URL Encode Decode — encoding reference
Related posts
- URL Encoding: The 7 Bugs That Break Your API — Every API has at least one URL-encoding bug. Here are the seven I see most — wha…
- Percent Encoding and RFC 3986 Explained — Why is `+` sometimes a space and sometimes a literal plus? Why does `%2520` show…
- Double URL Encoding — What It Is, Why It Happens, and How to Prevent It — Double URL encoding happens when an already-encoded URL is encoded again, turnin…
- URL Encode Decode — Encode and Decode URLs Online — URL encoding converts special characters to percent-encoded sequences (%20 for s…
- URL Encoding Special Characters — Which Characters to Encode and When — URL encoding converts special characters to percent-encoded sequences (%20, %26,…
Related tool
URL Encoder / Decoder
Percent-encode and decode URLs per RFC 3986.
Written by Mian Ali Khalid. Part of the Dev Productivity pillar.