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.

URL

How it works

  1. Parsing: Uses new URL(input) or new URL(input, base) from the WHATWG URL Standard. This is the same parser used by all modern browsers.
  2. Host normalization: International domain names are converted to ASCII via IDNA (Punycode). Default ports are omitted from the serialized URL.
  3. 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.
  4. Query handling: Spaces become %20, not +. For form-style encoding with +, use URLSearchParams.

URL components

PropertyDescription
hrefThe full serialized URL
originProtocol + host (no path/query/hash)
hostHostname + port (if non-default)
hostnameDomain name only (no port)
pathnamePath starting with /
searchQuery string including ?
hashFragment 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.

AspectWHATWG new URLencodeURI
SpecWHATWG URL Standard parser; UTS #46 ToASCII on a special-URL hostECMA-262 encoder; reserved URI characters stay intact
Hosthttps://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 porthttps://example.com:443/docs serializes as https://example.com/docsKeeps :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 spaceSample href https://user:pass@example.com:8080/docs?q=hello%20world#intro uses %20, not +Same %20 on that sample; reserved :/?#&=+ stay intact
Path UnicodeNFC é is /%C3%A9; NFD e + U+0301 is /e%CC%81. The path is not composed firstSame 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.

AspectThis page (new URL)URL.parse
ParserWHATWG API URL parserSame parser; not a second dialect
Invalid inputThrows 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 baseRejects /docsnull; with https://example.com the href is https://example.com/docs
Sample hrefParse of https://user:pass@example.com:8080/docs?q=hello world#intro writes https://user:pass@example.com:8080/docs?q=hello%20world#introSame href, host, path, query, and hash
Default HTTPS porthttps://example.com:443/docs serializes as https://example.com/docsSame 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')
// false

Legacy 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.

Related