URL Decoder_

Paste a URL or any percent-encoded text and read it in plain language: %20 becomes a space, %C3%A9 becomes é, and every query parameter is broken out into a name-and-value table. Malformed sequences don't kill the result — each one is reported with its exact position and left visible, and double-encoded input is detected with a one-click Decode again.

The mode switch matters: Query value decodes every sequence; Whole URL keeps the eleven reserved sequences intact so the address still works. Everything decodes in your browser — URLs full of tokens and session IDs never leave this tab.

toolkit.codes/url-decode
Query value: every %XX decodes
Paste a URL or encoded text to decode it
Decoded_Output
—
UTF-8
Ready
100% LOCAL
Input
A whole URL, a single query value, or any percent-encoded text — including input that is malformed, and input that has been encoded more than once.
Output
The decoded text, plus a name-and-value table for every query parameter it found, so a long address becomes a list you can actually read.
Processing
Decoded in this tab as you type. URLs carrying session identifiers and access tokens are exactly the sort of thing people paste into a decoder, and none of it leaves the page.
Limits
A malformed sequence never discards the result: it is reported with its exact position and left visible, so you can see what broke rather than losing everything after it.
Two modes
Query value decodes every sequence. Whole URL keeps the eleven reserved ones encoded, so a pasted address stays a working address instead of dissolving into the text around it.

URL decoding, done the way the URL means it

Why URLs are full of percent signs

A URL can only carry a small alphabet safely; everything else travels as percent-encoding — a % followed by two hex digits per byte of UTF-8. A space becomes %20, é becomes %C3%A9, 😀 becomes four sequences. Decoding online is the reverse trip, run live here as you type. This page is the decode half of a pair — the URL encoder is one click away for the return journey, and the two directions stay on separate pages because they need different defaults, not because one page could not hold both. In code, the same operation is decodeURIComponent() in JavaScript, urllib.parse.unquote() (or unquote_plus()) in Python, and rawurldecode() (or urldecode() for form data) in PHP — each language splitting the same two ways this page's controls do.

Query value vs whole URL — the choice that changes the answer

The top decoding mistake is not malformed input — it is the wrong mode. Take next=%2Fcart%3Fitem%3D7. As a query value, %2F is a slash and %3F a question mark: the parameter says "go to /cart?item=7". Decode the whole URL that contains it with the same aggression and those slashes and question marks merge into the surrounding address — the URL's structure is destroyed. That is why this page has two modes. Query value (decodeURIComponent semantics) decodes everything; Whole URL (decodeURI semantics) keeps the eleven reserved sequences — %23 %24 %26 %2B %2C %2F %3A %3B %3D %3F %40 — encoded, so a pasted address stays a working address while everything readable becomes readable.

The plus sign: a 1994 inheritance

+ means space only in application/x-www-form-urlencoded data — the format HTML forms have used since the beginning — and only in query strings and form bodies. In a path, + is just a plus: /c++/faq names the language, not "c faq". The toggle here applies form semantics to the whole input when you know that's what you have; the parameter table below the output doesn't wait for the toggle, because inside a parsed query string the context already decides — there, winter+boots is always "winter boots".

Double encoding: how %2520 happens

When layered systems each encode once — an app encodes a redirect target, a gateway encodes the whole URL again — the % of %20 becomes %25, producing %2520. One decode gives back %20, not the space you wanted. Recursive decoders just loop blindly; this page detects the situation instead: after decoding, if the result still contains decodable sequences, a banner offers Decode again, one deliberate layer at a time. That matters because "decode until nothing changes" can be wrong — a legitimate literal %20 in documentation text should survive, and only you know which layer is the real text.

Paste it, pick the mode, read the parameter table

  1. 01Paste the URL or encoded text — decoding runs live, and if the input parses as a URL or query string the parameter table fills in below the output automatically.
  2. 02Pick the mode first: Query value for a parameter, form field, or path segment; Whole URL for a complete address you want to keep clickable. The hint above the input restates what the active mode will do.
  3. 03Turn on + as space only for form-encoded data — the toggle applies everywhere, while the parameter table already applies form semantics inside query strings on its own.
  4. 04If the banner says the input looks double-encoded, click Decode_Again to peel one more layer, then Copy the result or Download it as a text file.

Four URLs and what reading them reveals

Reading a redirect chain

A login URL carries the destination in next= — decoded, you can see where it actually goes.

Input (query value mode)
next=%2Faccount%2Fbilling%3Fplan%3Dpro
Decoded
next=/account/billing?plan=pro

Auditing campaign parameters

An analytics URL from an email — the parameter table names every UTM without hand-splitting ampersands.

Input
https://x.dev/?utm_source=news%20letter&utm_campaign=spring+sale
Parameter table
utm_source = news letter
utm_campaign = spring sale

Un-mangling a double-encoded webhook

A payload went through two systems that each encoded once; the banner catches it.

Input
callback%253Fstatus%253Dpaid
After Decode again ×2
callback?status=paid

Checking a suspicious link

Phishing links hide the real target behind encoding; decoding before clicking shows the destination.

Input
https://short.ly/r?u=http%3A%2F%2F203.0.113.9%2Flogin
Parameter table reveals
u = http://203.0.113.9/login
(not the bank it claimed)

Percent-encoding quick reference

CharacterEncodedQuery value modeWhole URL mode
space%20 (or + in forms)decodesdecodes
/%2Fdecodesstays encoded
?%3Fdecodesstays encoded
&%26decodesstays encoded
=%3Ddecodesstays encoded
#%23decodesstays encoded
+%2Bdecodesstays encoded
%%25decodesdecodes
" · < · >%22 · %3C · %3Edecodesdecodes
é (UTF-8)%C3%A9decodesdecodes
😀 (UTF-8)%F0%9F%98%80decodesdecodes

“Stays encoded” is the point of Whole URL mode: those eleven characters are structure, and decoding them collapses the address. Everything non-structural decodes in both modes.

Which encoding rules apply where

ContextRules
Path segmentPercent-encoding only; + is a literal plus; / separates segments and must stay encoded inside one
Query name / valuePercent-encoding; + may mean space (form heritage); & and = are structure
Fragment (#…)Percent-encoding; never sent to the server — it stays in the browser
Form body (POST)application/x-www-form-urlencoded: spaces become +, newlines become %0D%0A
Cookie valuesNo single standard — most frameworks percent-encode; decode with Query value mode
HTTP headersMostly raw ASCII; percent-encoding appears in specific headers (Location, Link) as part of the URL

The same %XX machinery, five different dialects — which is why one decode button without a mode switch gets URLs wrong.

Habits that stop a URL being decoded twice

  • Decode a parameter value on its own (copy just the value, Query value mode) instead of the whole URL — you get the unambiguous answer without touching structure.
  • When a decoded result still looks wrong, check the issues panel before blaming the tool: one bare % from a truncated log line is usually the culprit.
  • Use the parameter table as a phishing check: the u=, url=, next=, and redirect= values show where a link really goes before you click it.
  • Peel double-encoded input one Decode_Again at a time and stop when it reads correctly — blind decode-until-stable can eat legitimate literal %20 text.
  • For batch work, paste many URLs one per line — percent-decoding is local, so every line decodes in place and the line count never changes.
  • Decoded control characters (%00, %1B) are invisible in most editors — the ␀-style markers here are your only visual warning before pasting somewhere unsafe.

When a decode succeeds and the answer is still wrong

The two modes really do differ

Query value decodes %2F to a slash; Whole URL leaves it encoded. Decode a full address in the wrong mode and paths merge, query strings split, and the URL stops working — the most common decoding error, and the reason the mode switch sits first in the toolbar.

+ is a space only in form contexts

In query strings and form bodies, + inherits the space meaning from 1994-era forms. In paths it is a literal plus: /c++/notes must not become /c /notes. Keep the toggle off unless you know the input is form-encoded; the parameter table applies the right rule automatically.

Double encoding compounds silently

Every layer that re-encodes turns % into %25 — two layers make %2520, three make %252520. Each Decode_Again peels exactly one. If your data shows this, fix the system that encodes twice; decoding around it forever is the workaround, not the cure.

A lone % breaks strict decoders

decodeURIComponent throws URIError on "100%" — one truncated sequence and your whole script rejects the input. This page decodes everything else and reports the position instead; in code, catch the error or pre-validate with a %[0-9A-F]{2} check.

%00 and control characters survive decoding

A decoded NUL or escape character is real and invisible — pasted into a terminal or log pipeline it can truncate strings or inject sequences. The output marks them with ␀-class symbols and counts them; treat any count above zero as a flag on untrusted input.

A debug URL usually carries a live token

URLs carry session IDs, password-reset tokens, and API keys in their query strings — exactly what you paste into a decoder while debugging. Several popular decoder sites process on their servers (one advertises 100 MB uploads, which only a server can take). This page decodes in your browser; the network tab will show nothing leaving.

Modes, malformed input, and what stays encoded

File handling
Uploads are read inside the page with the browser File API and are never transmitted; Download writes out what is already in the tab.
Modes
Query value (decodeURIComponent semantics — every sequence decodes) and Whole URL (decodeURI semantics — the eleven reserved sequences stay encoded)
Malformed input
Tolerant scanner: every decodable sequence decodes; bare %, incomplete %X, and invalid UTF-8 bytes are each reported with position and left as-is — nothing throws
Double encoding
Detected after decoding (the result would decode further); Decode_Again peels one layer per click — never a blind loop
Parameter table
Renders for URLs and query strings; names and values decoded with form semantics (+ = space) per the WHATWG URL standard
Character set
UTF-8 per RFC 3986 / WHATWG; legacy single-byte sequences (e.g. %E9 alone) are kept visible with a raw-byte hex note instead of a charset dropdown
Control characters
Decoded faithfully; displayed as ␀-class control pictures with a count — Copy carries the real bytes
Limits
100k-character URLs decode live; batch input decodes line-by-line by construction
Processing
Decoding, parsing, upload reading, and download generation run in your browser — input never leaves the tab

Questions about decoding URLs and query strings

How do I URL-decode in JavaScript?

decodeURIComponent(value) for parameters and form fields; decodeURI(url) for a complete address. Both throw URIError on malformed sequences like a lone % — wrap in try/catch for untrusted input, or split the string and decode piece by piece as this page does.

How do I URL-decode in Python?

urllib.parse.unquote(s) is the standard call; unquote_plus(s) additionally turns + into spaces for form data. To take a whole query string apart, parse_qs(s) gives you the same decoded name/value mapping as the parameter table on this page.

How do I URL-decode in PHP?

rawurldecode($s) is the RFC 3986 version; urldecode($s) additionally converts + to space, matching form encoding. The pair maps exactly to this page’s + as space toggle — off and on respectively.

Why did my + turn into a space (or not)?

Because + only means space in form-encoded contexts — query strings and POST bodies. Decoders that always convert mangle paths; decoders that never convert leave form data reading as winter+boots. Here you choose, and the parameter table applies the form rule automatically where it is guaranteed correct.

Is URL decoding the same as base64 decoding?

No — different schemes for different jobs. Percent-encoding escapes individual characters and stays readable; base64 rewrites whole byte streams in an alphabet of sixty-four characters. If your string ends in = signs, or arrives as one unbroken block with no percent signs at all, you want the base64 decoder — including for the base64url variant that uses - and _.

Does the URL I paste leave my browser?

No, and it matters more here than most places: debug URLs routinely carry a live session token in the query string, so a server-side decoder is handed a working login. The work is JavaScript running in this tab. Every function it calls is covered by a test that stubs fetch and XMLHttpRequest to throw, so a request that slipped in would break the build rather than reach a server — and you can confirm it for yourself by disconnecting and carrying on.