URL parser
Parse URLs using the WHATWG URL Standard to extract protocol, host, path, query, and hash.
Docify's URL parser uses the WHATWG URL Standard (new URL()) to break down URLs into their components: protocol, username, password, hostname, port, pathname, search (query string), and hash (fragment). The parser automatically applies IDNA encoding to international domain names (bücher.de → xn--bcher-kva.de) and percent-encodes Unicode in paths. Default ports (443 for HTTPS, 80 for HTTP) are normalized away. For relative URLs, provide a base URL. Nothing is uploaded or fetched.
How it works
- Parsing: Uses
new URL(input)ornew URL(input, base)from the WHATWG URL Standard. This is the same parser used by all modern browsers. - Host normalization: International domain names are converted to ASCII via IDNA (Punycode). Default ports are omitted from the serialized URL.
- Path encoding: Non-ASCII characters in the path are percent-encoded. The path is not NFC-normalized first, so é and e+combining-accent produce different encodings.
- Query handling: Spaces become
%20, not+. For form-style encoding with +, use URLSearchParams.
URL components
| Property | Description |
|---|---|
href | The full serialized URL |
origin | Protocol + host (no path/query/hash) |
host | Hostname + port (if non-default) |
hostname | Domain name only (no port) |
pathname | Path starting with / |
search | Query string including ? |
hash | Fragment including # |
WHATWG new URL vs encodeURI
This page runs new URL(input) or new URL(input, base) from the WHATWG URL Standard. It splits host, path, query, and fragment, then applies UTS #46 ToASCII to a special-URL host. It does not call encodeURI on the whole string — that ECMA-262 encoder lives on the URL encoder and never looks at scheme or host. For the longer comparison, see WHATWG new URL vs encodeURI: Punycode vs Percent-Encoding.
| Aspect | WHATWG new URL | encodeURI |
|---|---|---|
| Spec | WHATWG URL Standard parser; UTS #46 ToASCII on a special-URL host | ECMA-262 encoder; reserved URI characters stay intact |
| Host | https://bücher.de/ hostname is xn--bcher-kva.de; href is https://xn--bcher-kva.de/ | Percent-encodes the Unicode label: https://b%C3%BCcher.de/ |
| Default port | https://example.com:443/docs serializes as https://example.com/docs | Keeps :443 |
| Relative URL | /docs plus https://example.com is https://example.com/docs; without a base this page rejects with “Invalid URL. new URL() needs an absolute URL (scheme + host), or a relative URL plus an absolute base.” | /docs stays /docs |
| Query space | Sample href https://user:pass@example.com:8080/docs?q=hello%20world#intro uses %20, not + | Same %20 on that sample; reserved :/?#&=+ stay intact |
| Path Unicode | NFC é is /%C3%A9; NFD e + U+0301 is /e%CC%81. The path is not composed first | Same two path encodings; host NFC / NFD stay percent-encoded (https://%C3%A9.example/ vs https://e%CC%81.example/) |
Parse of https://user:pass@example.com:8080/docs?q=hello world#intro writes that sample href. Host NFC é and NFD e + U+0301 both become xn--9ca.example here because UTS #46 runs NFC first; encodeURI keeps those two host encodings distinct. This page is not encodeURIComponent and not a fetch. Form-style query pairs (space → +) belong on URLSearchParams; UTS #46 on a host string belongs on IDNA; RFC 3492 without a URL split belongs on Punycode.
Parse URLs in Python and Node
The snippets below split an absolute URL into host, path, query, and fragment. Node's new URL() is the same WHATWG URL Standard this page uses. CPython urllib.parse.urlsplit is RFC 3986-style — the stdlib has no WHATWG parser. For how WHATWG parsing differs from percent-encoding a string, see WHATWG new URL vs encodeURI: Punycode vs Percent-Encoding.
Node.js with new URL
Node 10+ ships the WHATWG URL class (also as import { URL } from 'node:url'). A space in the query becomes %20 on href and search. Pass a second argument to resolve a relative path. Avoid the legacy url.parse helper — it is not this parser:
const url = new URL('https://example.com:8080/docs?q=hello world#intro');
url.href
// "https://example.com:8080/docs?q=hello%20world#intro"
url.host
// "example.com:8080"
url.pathname
// "/docs"
url.search
// "?q=hello%20world"
url.hash
// "#intro"
const relative = new URL('/docs', 'https://example.com');
relative.href
// "https://example.com/docs"Python with urllib.parse.urlsplit
urlsplit returns scheme, netloc, path, query, and fragment. urlparse is the same plus a ;params field from RFC 1738 — skip it for http(s). query and fragment omit the leading ? / #, and port is an int. Use urljoin for a relative path:
from urllib.parse import urlsplit, urljoin
parts = urlsplit('https://example.com:8080/docs?q=hello world#intro')
# SplitResult(scheme='https', netloc='example.com:8080',
# path='/docs', query='q=hello world', fragment='intro')
parts.port
# 8080
parts.query
# 'q=hello world'
parts.geturl()
# 'https://example.com:8080/docs?q=hello world#intro'
urljoin('https://example.com', '/docs')
# 'https://example.com/docs'geturl() keeps the space in the query; this page and Node serialize ?q=hello%20world. WHATWG applies IDNA to the hostname (bücher.de → xn--bcher-kva.de) and drops default ports (https://example.com:443 serializes without :443). urlsplit keeps the Unicode host and the :443. Percent-encode a string on the URL encoder; form-style query pairs belong on URLSearchParams.
WHATWG URL.parse vs new URL
This page still calls new URL(input) or new URL(input, base) and maps a thrown TypeError to the message above. WHATWG URL.parse is the same API URL parser: success is a URL object; failure is null, not a throw. URL.canParse returns the boolean for those same inputs. Confirmed here with Node 22.14. CPython has no URL.parse — urllib.parse.urlsplit is the RFC 3986 split in the section above, not this method. Node require('node:url').parse is the legacy helper this page already warns against; it is not the WHATWG static method. For how WHATWG parsing differs from percent-encoding a string, see WHATWG new URL vs encodeURI: Punycode vs Percent-Encoding.
| Aspect | This page (new URL) | URL.parse |
|---|---|---|
| Parser | WHATWG API URL parser | Same parser; not a second dialect |
| Invalid input | Throws TypeError; this page shows “Invalid URL. new URL() needs an absolute URL (scheme + host), or a relative URL plus an absolute base.” | null (URL.parse('not a url')) |
| Relative without base | Rejects /docs | null; with https://example.com the href is https://example.com/docs |
| Sample href | Parse of https://user:pass@example.com:8080/docs?q=hello world#intro writes https://user:pass@example.com:8080/docs?q=hello%20world#intro | Same href, host, path, query, and hash |
| Default HTTPS port | https://example.com:443/docs serializes as https://example.com/docs | Same omitted :443 |
Node.js with URL.parse
Node 22.1+ ships WHATWG URL.parse (browsers from late 2024). URL.canParse is older (Node 18.17 / 19.9). Neither is url.parse from node:url:
const sample = 'https://user:pass@example.com:8080/docs?q=hello world#intro';
const parsed = URL.parse(sample);
parsed.href
// "https://user:pass@example.com:8080/docs?q=hello%20world#intro"
URL.parse('not a url')
// null
URL.parse('/docs')
// null
URL.parse('/docs', 'https://example.com').href
// "https://example.com/docs"
URL.canParse(sample)
// true
URL.canParse('/docs')
// falseLegacy url.parse('not a url') returns an object whose href is not%20a%20url— not null and not a throw. url.parse('https://example.com:443/docs') keeps :443; this page and URL.parse drop it. urlsplit('not a url') is a SplitResult with path not a url. There is no POSIX or jq WHATWG parser — curl --url-query belongs on URLSearchParams, and %{url.host} is a curl write-out, not this parser. IDNA on a host string belongs on IDNA.
FAQ
- Does this tool upload my URL?
- No. All parsing runs in your browser using the native URL constructor. Nothing is sent to a server, and the URL is not fetched.
- What URL standard does this use?
- This tool uses the WHATWG URL Standard via new URL(). This is the same parser used by browsers, Node.js, and Deno. It handles special schemes (http, https, ftp, etc.) with their default ports.
- How are international domain names handled?
- new URL() automatically applies IDNA (UTS #46) to the hostname. bücher.de becomes xn--bcher-kva.de in the parsed output. Unicode in the path is percent-encoded.
- What is the difference between query and searchParams?
- search is the raw query string including the ? prefix (e.g., ?q=hello%20world). searchParams shows the decoded key=value pairs. Spaces in URLs are %20, not + (form encoding uses +).
- Can I parse relative URLs?
- Yes. Enter a relative path like /docs and provide a base URL like https://example.com. The parser will resolve the full URL.
- Why does port disappear for https://example.com:443?
- The URL Standard normalizes default ports. Port 443 for HTTPS and port 80 for HTTP are omitted from the serialized URL because they are the defaults.