URL Decode: Percent-Encoding to Text

h
104
h
e
101
e
l
108
l
l
108
l
o
111
o
%20
32
(space)
w
119
w
o
111
o
r
114
r
l
108
l
d
100
d

text

Swap to text to percent-encoding

Percent-encode a string for a URL

How do you decode an encoded URL?

URL decoding reads those triplets back as the characters they stand for. Decoding reverses the triplets one byte at a time and then reads the resulting bytes as UTF-8, so two triplets can produce a single character.

In short

%20 decodes to a space. URL decoding reads each %XX triplet as one byte, then reads the bytes as UTF-8, so a two-triplet pair such as %C3%A9 comes back as the single character e-acute.

  1. Find each percent sign. It opens a triplet: the percent and the 2 hexadecimal digits after it.
  2. Read those two digits as one byte, and leave every other character alone.
  3. Collect the bytes in the order they appeared.
  4. Read the run of bytes as UTF-8, which is why two triplets can give one character.
  5. Check the result for a leftover %25. hello%20world becomes hello world.

Percent-Encoding to Text Conversion Table

The reserved characters are the general delimiters : / ? # [ ] @ and the sub-delimiters ! $ & ' ( ) * + , ; = . Each one is safe where it acts as a delimiter and must be percent-encoded where it is data. The unreserved set is the ASCII letters, the digits, hyphen (-), period (.), underscore (_) and tilde (~). These characters never need percent-encoding, and a normaliser decodes them if it finds them encoded. Read the row for the character you have: the Triplet column is what a URL carries in its place. ASCII codes 32–126 are printable characters; 0–31 and 127 are control characters (for example 10 = line feed, 13 = carriage return, 32 = space). That row is the one everybody meets first.

Percent-Encoding to Text Conversion Table
CharacterNameRoleTriplet
A-Z a-z 0-9 - . _ ~unreservednot encoded
:the colongeneral delimiter%3A
/the slashgeneral delimiter%2F
?the question markgeneral delimiter%3F
#the number signgeneral delimiter%23
[the left bracketgeneral delimiter%5B
]the right bracketgeneral delimiter%5D
@the at signgeneral delimiter%40
!the exclamation marksub-delimiter%21
$the dollar signsub-delimiter%24
&the ampersandsub-delimiter%26
'the apostrophesub-delimiter%27
(the left parenthesissub-delimiter%28
)the right parenthesissub-delimiter%29
*the asterisksub-delimiter%2A
+the plus signsub-delimiter%2B
,the commasub-delimiter%2C
;the semicolonsub-delimiter%3B
=the equals signsub-delimiter%3D
(space)the spacenot allowed in a URI%20
%the percent signstarts a triplet%25

The same value in each encoding

These rows are shared with the other encoders, so a value written here is the value every page in the family writes.

The same value in each encoding
ValueWritten as
hello worldtext 'hello world' = percent 'hello%20world' = base64 'aGVsbG8gd29ybGQ='
spacecharacter ' ' = percent '%20' = base64 'IA==' = HTML no character reference needed
apostrophecharacter ''' = percent '%27' = base64 'Jw==' = HTML '
écharacter 'é' = percent '%C3%A9' = base64 'w6k=' = HTML é or é

Worked Example: hello%20world to Text

One character at a time, with the working written out. This is the same grid the converter shows when you turn on "show steps".

h
104
h
e
101
e
l
108
l
l
108
l
o
111
o
%20
32
(space)
w
119
w
o
111
o
r
114
r
l
108
l
d
100
d

text

Result: hello%20world = hello world

For characters outside US-ASCII, RFC 3986 says to write the text as UTF-8 octets first and then percent-encode every octet that is not in the unreserved set, which is why é becomes %C3%A9.

Read the grid a row at a time: h is 68 and is written h; e is 65 and is written e; l is 6C and is written l. A character that is already unreserved keeps itself, so most of a readable string passes through untouched and only the characters a URL would misread change at all.

é is U+00E9 and encodes as C3 A9 (2 bytes); € is U+20AC and encodes as E2 82 AC (3 bytes); 😀 is U+1F600 and encodes as F0 9F 98 80 (4 bytes). UTF-8 encodes each character in 1 to 4 bytes. Characters 0–127 use one byte identical to ASCII, so ASCII text is valid UTF-8. Those byte counts are what decide how many triplets a character takes, which is why one character can arrive as two triplets and still be one character when it is read back.

Check it in reverse

A percent-encoded octet is a triplet: a percent sign followed by the two hexadecimal digits of that byte's value. Read the triplets in the order they appear and the original text comes back whole, because an unreserved character was never changed and a triplet always spells exactly one byte. To verify the result by hand, percent-encode the decoded text again and compare it with what you started from.

Worked examples: percent-encoding to text

Each example gives the value first, then the working. Click a value to load it in the converter.

What is %27 URL-encoded?

%27 in percent-encoding is ' in text.

Method: read each triplet's two digits as one byte, then read the bytes as UTF-8.

Load this value in the converter

What is %23 encoded?

%23 in percent-encoding is # in text.

Method: read each triplet's two digits as one byte, then read the bytes as UTF-8.

Load this value in the converter

What does %2F decode to?

%2F in percent-encoding is / in text.

Method: read each triplet's two digits as one byte, then read the bytes as UTF-8.

Load this value in the converter

What does %C3%A9 decode to?

%C3%A9 in percent-encoding is é in text.

Method: read each triplet's two digits as one byte, then read the bytes as UTF-8.

Load this value in the converter

What does %20 decode to?

%20 in percent-encoding is (space) in text.

Method: read each triplet's two digits as one byte, then read the bytes as UTF-8.

Load this value in the converter

What does %26 decode to?

%26 in percent-encoding is & in text.

Method: read each triplet's two digits as one byte, then read the bytes as UTF-8.

Load this value in the converter

What does %3D decode to?

%3D in percent-encoding is = in text.

Method: read each triplet's two digits as one byte, then read the bytes as UTF-8.

Load this value in the converter

What does %3F decode to?

%3F in percent-encoding is ? in text.

Method: read each triplet's two digits as one byte, then read the bytes as UTF-8.

Load this value in the converter

What does %3A decode to?

%3A in percent-encoding is : in text.

Method: read each triplet's two digits as one byte, then read the bytes as UTF-8.

Load this value in the converter

What does %40 decode to?

%40 in percent-encoding is @ in text.

Method: read each triplet's two digits as one byte, then read the bytes as UTF-8.

Load this value in the converter

What does %2B decode to?

%2B in percent-encoding is + in text.

Method: read each triplet's two digits as one byte, then read the bytes as UTF-8.

Load this value in the converter

What does hello%20world decode to?

hello%20world in percent-encoding is hello world in text.

Method: read each triplet's two digits as one byte, then read the bytes as UTF-8.

Load this value in the converter

Every example above reverses exactly - write the same characters back as triplets.

What Changes the Decoded Result?

Is the character data or a delimiter?

That is the whole decision, and only you know it. A slash between path segments is a delimiter and stays; a slash inside a value is data and becomes %2F.

Has the value been through the encoder twice?

A percent sign is itself percent-encoded as %25, so a string that has been through the encoder twice shows %2520 where one space should be.

What case are the hexadecimal digits?

0b marks binary, 0o octal, 0x hexadecimal; a leading # marks a hex colour. Hexadecimal is not case-sensitive (FF = ff); iToolHub writes hex digits in uppercase.

Is this the encoding you need at all?

The three schemes protect different things and are not interchangeable. these encodings are not interchangeable - why a string can be encoded more than one way sets out which does what.

Percent-Encoding to Text in Code

Both standard libraries carry this conversion, so the one-liner below is the whole job. Check the result against the table above before you trust it in a pipeline.

Python: urllib.parse.unquote('hello%20world')
JavaScript: decodeURIComponent('hello%20world')

Common Mistakes

Frequently Asked Questions

What is a double-encoded URL?

A URL that went through the encoder twice. A percent sign is itself percent-encoded as %25, so a string that has been through the encoder twice shows %2520 where one space should be.

Does a plus sign decode to a space?

Not on this page. A plus sign comes back as a plus sign, and a space arrives as %20, so a value built by an HTML form needs its plus signs turned back into spaces first.

Why do two triplets sometimes decode to one character?

Decoding reverses the triplets one byte at a time and then reads the resulting bytes as UTF-8, so two triplets can produce a single character.

Can you URL decode a Base64 string?

You can, and it usually changes nothing: the Base64 alphabet has no percent sign in it. The exception is a value a URL encoded on top, where %2B and %2F come back as plus and slash.

How do you URL decode in Python or PHP?

urllib.parse.unquote('hello%20world') in Python and urldecode('hello%20world') in PHP both return hello world. Both read each triplet as one byte before reading the bytes as text.

"Can I go back the other way?" Yes. The same value converts straight back in the text to percent-encoding direction.

The rest of the family sits on the rest of the text encoding converters.