urllib.parse.unquote

The reverse of quote. unquote decodes %XX escapes and leaves + alone; unquote_plus decodes form data, where + means a space. Invalid UTF-8 never raises by default — errors='replace' turns it into the U+FFFD replacement character.

urllib.parse functionPython 3.0+Live demo
Common call
unquote('caf%C3%A9')
Returns
'café'
Replaces
decoding %XX escapes by hand
Watch out
unquote keeps + as +; form data needs unquote_plus
urllib.parse.unquote(stringstring — Percent-encoded text. bytes are accepted by unquote since 3.9 (not by unquote_plus). A % that is not followed by two hex digits is kept as it is.type: str | bytes · required, encodingencoding — How the decoded bytes are turned into characters.type: str · default: 'utf-8'='utf-8', errorserrors — What to do with bytes that are not valid in that encoding: 'replace' (U+FFFD), 'strict' (raise UnicodeDecodeError), 'ignore', …type: str · default: 'replace'='replace')
→ str

Demo

Live evaluation
The same encoded text decoded both ways. Only unquote_plus turns + into a space.
Try:
Inputs
textstrpercent-encoded text
Code
from urllib.parse import unquote, unquote_plus
(unquote('rock+%26+roll'), unquote_plus('rock+%26+roll'))
Result
('rock+&+roll', 'rock & roll')

unquote decodes the %XX escapes as one block of bytes and then decodes those bytes as UTF-8, so %C3%A9 is a single é. A % that is not followed by two hex digits — '100% sure', '%zz' — is left exactly as it is, never an error. When the bytes are not valid UTF-8 (%E9 alone is Latin-1 é), the default errors='replace' puts U+FFFD in their place.

Parameters

NameTypeRequiredDescription
stringstr | bytesyesPercent-encoded text. bytes are accepted by unquote since 3.9 (not by unquote_plus). A % that is not followed by two hex digits is kept as it is.
encodingstrno ('utf-8')How the decoded bytes are turned into characters.
errorsstrno ('replace')What to do with bytes that are not valid in that encoding: 'replace' (U+FFFD), 'strict' (raise UnicodeDecodeError), 'ignore', …

Return value

str — The text with every valid %XX escape decoded. unquote_to_bytes returns bytes instead.

Common patterns

Decode a form-encoded value
HTML forms send spaces as +.
from urllib.parse import unquote_plus
name = unquote_plus(raw_value)
Read a percent-encoded path segment
Split on / first, then decode each segment, so an encoded %2F stays inside its segment.
from urllib.parse import unquote, urlsplit
segments = [unquote(s) for s in urlsplit(url).path.split('/')]
Fail loudly on bad UTF-8
errors='strict' raises instead of inserting U+FFFD.
from urllib.parse import unquote
text = unquote(value, errors='strict')

Examples

1. Decode UTF-8 escapes
from urllib.parse import unquote unquote('caf%C3%A9%20%E2%82%AC')
Returns
'café €'
2. unquote keeps +
from urllib.parse import unquote unquote('rock+%26+roll')
Returns
'rock+&+roll'
3. unquote_plus: + is a space
from urllib.parse import unquote_plus unquote_plus('rock+%26+roll')
Returns
'rock & roll'
4. Invalid escapes stay
from urllib.parse import unquote unquote('100% %zz%4')
Returns
'100% %zz%4'
5. Raw bytes
from urllib.parse import unquote_to_bytes unquote_to_bytes('a%20b%FF')
Returns
b'a b\xff'
6. bytes input (3.9+)
from urllib.parse import unquote unquote(b'a%20b')
Returns
'a b'
7. Double-encoded text needs two passes
from urllib.parse import unquote (unquote('%2541'), unquote(unquote('%2541')))
Returns
('%41', 'A')

Pitfalls

1. unquote on form data
Browsers encode a space in a form field as +. unquote leaves it, so names come out as Ada+Lovelace.
unquote
from urllib.parse import unquote
unquote('Ada+Lovelace')
'Ada+Lovelace'
unquote_plus
from urllib.parse import unquote_plus
unquote_plus('Ada+Lovelace')
'Ada Lovelace'
2. Silent U+FFFD for a different encoding
Text encoded as Latin-1 (%E9 for é) is not valid UTF-8. The default errors='replace' hides the problem; say the encoding when you know it.
default utf-8
from urllib.parse import unquote
unquote('caf%E9')
'caf�'
encoding='latin-1'
from urllib.parse import unquote
unquote('caf%E9', encoding='latin-1')
'café'
3. unquote_plus with bytes
unquote accepts bytes, but unquote_plus calls string.replace('+', ' ') with str arguments, which bytes reject.
unquote_plus(bytes)
from urllib.parse import unquote_plus
unquote_plus(b'a+b')
TypeError: a bytes-like object is required, not 'str'
replace first
from urllib.parse import unquote
unquote(b'a+b'.replace(b'+', b' '))
'a b'

When to use

Use it
  • Reading a percent-encoded path segment or value: unquote
  • Reading form data or a query value by hand: unquote_plus
  • Binary data in a URL: unquote_to_bytes
Reach for something else
  • A whole query string → parse_qs / parse_qsl (they decode with unquote_plus)
  • Decoding a full URL before splitting it → urlsplit first, then decode the parts

Notes

CPython impl
unquote finds runs of ASCII characters, turns each run into bytes with unquote_to_bytes and decodes them; non-ASCII characters in the input are passed through untouched
Errors
errors defaults to 'replace', not 'strict' — unquote never raises for bad UTF-8 unless you ask
Invalid %
A % not followed by two hex digits is copied literally

FAQ

urllib.parse.unquote('caf%C3%A9') returns 'café'. For form data or query values, where + means a space, use unquote_plus.