Every URL is a structured string with well-defined parts. Parsing a URL means splitting it into those parts so you can read, modify, or reconstruct it safely — without fragile string splits or regex hacks.
URL anatomy
Take this example URL:
https://www.example.com:8080/search?q=hello+world&lang=en#results
| Component | Value | Name |
|---|---|---|
https |
protocol | scheme |
www.example.com |
hostname | host |
8080 |
port | port |
/search |
path | pathname |
q=hello+world&lang=en |
query string | search |
results |
hash target | fragment / hash |
The ? and # are delimiters — they don't belong to the query string or fragment themselves.
JavaScript: URL and URLSearchParams
The browser-native URL constructor is the modern standard. It works in all current browsers and in Node.js (since v10).
const url = new URL("https://www.example.com:8080/search?q=hello+world&lang=en#results");
console.log(url.protocol); // "https:"
console.log(url.hostname); // "www.example.com"
console.log(url.port); // "8080"
console.log(url.pathname); // "/search"
console.log(url.search); // "?q=hello+world&lang=en"
console.log(url.hash); // "#results"
console.log(url.origin); // "https://www.example.com:8080"
Parsing query parameters with URLSearchParams:
const params = new URLSearchParams(url.search);
params.get("q"); // "hello world" (decoded automatically)
params.get("lang"); // "en"
// Iterate all params
for (const [key, value] of params) {
console.log(key, value);
}
// Check existence
params.has("q"); // true
params.has("page"); // false
URLSearchParams handles decoding automatically — hello+world becomes "hello world" and %C3%A9 becomes "é".
Modifying and rebuilding a URL:
const url = new URL("https://example.com/search?q=old");
url.searchParams.set("q", "new query");
url.searchParams.append("page", "2");
url.pathname = "/results";
console.log(url.toString());
// "https://example.com/results?q=new+query&page=2"
Relative URLs require a base:
const url = new URL("/about", "https://example.com");
// "https://example.com/about"
Error handling — new URL() throws a TypeError for invalid URLs:
function tryParseUrl(raw) {
try {
return new URL(raw);
} catch {
return null;
}
}
Python: urllib.parse
Python's standard library urllib.parse covers all URL parsing needs.
from urllib.parse import urlparse, parse_qs, parse_qsl, urlencode, urlunparse
raw = "https://www.example.com:8080/search?q=hello+world&lang=en#results"
parts = urlparse(raw)
print(parts.scheme) # "https"
print(parts.netloc) # "www.example.com:8080"
print(parts.hostname) # "www.example.com"
print(parts.port) # 8080 (int, not string)
print(parts.path) # "/search"
print(parts.query) # "q=hello+world&lang=en"
print(parts.fragment) # "results"
Parsing query parameters:
from urllib.parse import parse_qs, parse_qsl
# parse_qs: values are lists (handles repeated keys)
params = parse_qs(parts.query)
print(params) # {'q': ['hello world'], 'lang': ['en']}
print(params["q"][0]) # "hello world"
# parse_qsl: list of (key, value) tuples — preserves order
for key, value in parse_qsl(parts.query):
print(key, value)
parse_qs always returns lists because a key can appear multiple times in a query string (e.g. ?tag=a&tag=b).
Rebuilding a URL:
from urllib.parse import urlunparse
new_query = urlencode({"q": "new query", "page": "2"})
rebuilt = urlunparse((
parts.scheme,
parts.netloc,
parts.path,
"", # params (rare; used for path params)
new_query,
"" # fragment
))
print(rebuilt)
# "https://www.example.com:8080/search?q=new+query&page=2"
Resolving relative URLs:
from urllib.parse import urljoin
base = "https://example.com/docs/v1/"
print(urljoin(base, "guide")) # "https://example.com/docs/v1/guide"
print(urljoin(base, "/about")) # "https://example.com/about"
print(urljoin(base, "https://other.com")) # "https://other.com"
Go: net/url
Go's net/url package parses URLs into a *url.URL struct.
package main
import (
"fmt"
"net/url"
)
func main() {
raw := "https://www.example.com:8080/search?q=hello+world&lang=en#results"
u, err := url.Parse(raw)
if err != nil {
panic(err)
}
fmt.Println(u.Scheme) // "https"
fmt.Println(u.Host) // "www.example.com:8080"
fmt.Println(u.Hostname()) // "www.example.com"
fmt.Println(u.Port()) // "8080"
fmt.Println(u.Path) // "/search"
fmt.Println(u.RawQuery) // "q=hello+world&lang=en"
fmt.Println(u.Fragment) // "results"
}
Parsing query parameters:
params, _ := url.ParseQuery(u.RawQuery)
// or: params := u.Query() (same result)
fmt.Println(params.Get("q")) // "hello world"
fmt.Println(params.Get("lang")) // "en"
// Repeated keys
for _, v := range params["tag"] {
fmt.Println(v)
}
url.Values is map[string][]string — same as Python's parse_qs, values are slices.
Modifying and rebuilding:
params := u.Query()
params.Set("q", "new query")
params.Add("page", "2")
u.RawQuery = params.Encode()
u.Path = "/results"
fmt.Println(u.String())
// "https://www.example.com:8080/results?lang=en&page=2&q=new+query"
Note: url.Parse is lenient and rarely returns an error. Use url.ParseRequestURI when you need strict validation (rejects relative URLs and bare paths).
PHP: parse_url
PHP's parse_url returns an associative array. Keys are absent (not null) when a component doesn't exist.
<?php
$raw = "https://www.example.com:8080/search?q=hello+world&lang=en#results";
$parts = parse_url($raw);
echo $parts['scheme']; // "https"
echo $parts['host']; // "www.example.com"
echo $parts['port']; // 8080 (int)
echo $parts['path']; // "/search"
echo $parts['query']; // "q=hello+world&lang=en"
echo $parts['fragment']; // "results"
Parsing query parameters:
parse_str($parts['query'], $params);
echo $params['q']; // "hello world"
echo $params['lang']; // "en"
// Repeated keys need array syntax in the query string: tag[]=a&tag[]=b
Rebuilding a URL (PHP has no http_build_url by default, build it manually):
function buildUrl(array $parts): string {
$url = '';
if (isset($parts['scheme'])) $url .= $parts['scheme'] . '://';
if (isset($parts['host'])) $url .= $parts['host'];
if (isset($parts['port'])) $url .= ':' . $parts['port'];
if (isset($parts['path'])) $url .= $parts['path'];
if (isset($parts['query'])) $url .= '?' . $parts['query'];
if (isset($parts['fragment'])) $url .= '#' . $parts['fragment'];
return $url;
}
$parts['query'] = http_build_query(['q' => 'new query', 'page' => 2]);
echo buildUrl($parts);
// "https://www.example.com:8080/search?q=new+query&page=2"
Quick reference
| Task | JavaScript | Python | Go | PHP |
|---|---|---|---|---|
| Parse URL | new URL(raw) |
urlparse(raw) |
url.Parse(raw) |
parse_url($raw) |
| Get hostname | url.hostname |
p.hostname |
u.Hostname() |
$p['host'] |
| Get path | url.pathname |
p.path |
u.Path |
$p['path'] |
| Get query string | url.search |
p.query |
u.RawQuery |
$p['query'] |
| Parse query params | new URLSearchParams(url.search) |
parse_qs(p.query) |
u.Query() |
parse_str($q, $p) |
| Get one param | params.get("key") |
params["key"][0] |
params.Get("key") |
$params['key'] |
| Rebuild URL | url.toString() |
urlunparse(parts) |
u.String() |
manual |
| Resolve relative | new URL(rel, base) |
urljoin(base, rel) |
base.ResolveReference(ref) |
— |
Common mistakes
Splitting on ? or # with string methods. url.split("?")[1] breaks as soon as the URL has a fragment, no port, or encoded ? in a parameter value. Use a proper parser — it handles all edge cases.
Forgetting that query parameter values are decoded. URLSearchParams.get("q") returns "hello world", not "hello+world". If you compare against the raw string, the match fails.
PHP's repeated key syntax. PHP's parse_str silently drops duplicate keys unless they use bracket notation (tag[]=a&tag[]=b). If you receive query strings from an external source, check for dropped data.
Go's url.Parse accepting invalid URLs. url.Parse("://broken") may not error — use url.ParseRequestURI or validate u.Host != "" explicitly.
Port is a string in some languages. In JavaScript, url.port is a string ("8080", or "" if it's the default). In Go, u.Port() is also a string. Only PHP and Python return a numeric port.
parse_url returns false on malformed input. PHP's parse_url("not a url at all") may return false instead of an array. Always check the return value before accessing keys.
FAQ
Q: Does new URL() work in Node.js?
Yes. URL is globally available in Node.js since v10.0 (without importing anything). In older Node.js (v7–v9) you needed require('url').URL.
Q: How do I parse a URL from a browser's address bar?
window.location is already a parsed URL object with .hostname, .pathname, .search, .hash, etc. You don't need new URL() for the current page.
Q: How do I handle query parameters that appear multiple times?
All languages treat repeated keys as arrays/lists: params.getAll("tag") in JS, params["tag"] (a list) in Python, params["tag"] (a slice) in Go. PHP requires bracket notation (tag[]=a&tag[]=b) for arrays.
Q: How do I check if a URL is absolute or relative?
In JS, new URL(str) throws for relative URLs — use a try/catch. In Python, urlparse returns a result with an empty scheme for relative paths. In Go, url.Parse succeeds for both; check u.IsAbs(). In PHP, check isset(parse_url($url)['host']).
Q: How do I URL-encode a value before inserting it into a URL?
Use encodeURIComponent (JS), urllib.parse.quote (Python), url.QueryEscape (Go), or rawurlencode (PHP). For a full guide, see How to URL Encode a String.
Q: Can I parse a URL without a library?
Technically yes, but you shouldn't. URL syntax has many edge cases: IPv6 addresses in brackets ([::1]), encoded characters in hostnames, default ports that should be omitted, path normalization (. and ..). Built-in parsers handle all of them correctly.