Skip to content
LocalOnly
Developer Utilities

How to Decode a URL Parameter That Contains Another URL

Tracking links and login redirects nest one URL inside another, encoded once per layer. Here is how to unwrap them correctly in JavaScript and Python, and the redirect check that keeps it safe.

Updated October 18, 20262 min readTool runs in your browser

To decode a URL parameter that contains another URL, you need to unwrap one layer at a time. The URL Decoder decodes text in the browser, and its page explains how to tell how many times something was encoded. This guide works through a real-world shape: a tracking link that wraps a login link that wraps the page you actually want.

A URL inside a URL inside a URL

An email links to a click tracker. The tracker forwards to a login page, and the login page redirects to the cart once you sign in. Each hop carries the next one as an encoded parameter:

https://t.example.com/c?u=https%3A%2F%2Fauth.example.com%2Flogin%3Fredirect_uri%3Dhttps%253A%252F%252Fshop.example.com%252Fcart%253Fcoupon%253DSAVE%252010%2526ref%253Demail

The inner URL was encoded twice, once for each level it is nested in, which is why it shows %253A instead of %3A: %25 is an encoded %.

Decode URL parameters in JavaScript

Let the URL object do the decoding. searchParams.get decodes exactly one layer, which is exactly what each step needs:

const login = new URL(tracking).searchParams.get("u");
// "https://auth.example.com/login?redirect_uri=https%3A%2F%2Fshop.example.com%2Fcart%3Fcoupon%3DSAVE%2010%26ref%3Demail"

const cart = new URL(login).searchParams.get("redirect_uri");
// "https://shop.example.com/cart?coupon=SAVE 10&ref=email"

Do not call decodeURIComponent on top of searchParams.get. The value is already decoded once, and a second decode turns any real % in the data, such as a coupon named 50%OFF, into a broken escape or an error.

Decode URL parameters in Python

from urllib.parse import parse_qs, urlsplit

login = parse_qs(urlsplit(tracking).query)["u"][0]
cart = parse_qs(urlsplit(login).query)["redirect_uri"][0]
# 'https://shop.example.com/cart?coupon=SAVE 10&ref=email'

parse_qs decodes one layer and returns a list per key, because a parameter can repeat. Take the first value only when you expect exactly one.

When a value looks encoded twice

Sometimes a value arrives with %2520 where you expect a space: it was encoded twice by mistake, not nested on purpose. One decode gives %20, a second gives the space. Fix it where it happens, in the code that encodes an already encoded value, rather than adding a second decode on the reading side. A blanket double decode corrupts every value that legitimately contains a %.

Check where a redirect points

A decoded redirect_uri or next parameter is input from whoever built the link. If your login page sends users wherever it says, an attacker can send a real login link of yours that ends on their own site. This is called an open redirect. Parse the decoded URL and allow only your own hosts:

const ALLOWED_HOSTS = new Set(["shop.example.com"]);
const target = new URL(cart);
if (target.protocol !== "https:" || !ALLOWED_HOSTS.has(target.hostname)) {
  throw new Error("redirect not allowed");
}

Frequently asked questions

Command Palette

Search for a tool or command