A URL Parser and Query String Editor With Live Rebuild
Paste a URL, see it decomposed into protocol, host, port, path, query params, and hash. Edit any piece — or add/remove/update query params in a table — and the URL rebuilds live. Plus a percent-encoding panel for when you need to manually encode a value.
Working with URLs is surprisingly finicky. Query params with special characters need encoding. Repeated keys (like ?tag=a&tag=b) need array handling. Editing query params by string manipulation is error-prone. A structured editor removes the guesswork.
🔗 Live demo: https://sen.ltd/portfolio/url-parser/
📦 GitHub: https://github.com/sen-ltd/url-parser
Features:
- Parse URL into components
- Live-edit each component (protocol, host, port, path, hash)
- Query param table editor (add/remove/update/reorder)
- Percent encode/decode tools
- URL validation
- Handles IPv6, repeated keys, special chars
- Japanese / English UI
- Zero dependencies, 66 tests
Wrapping the URL API
Modern browsers have a built-in URL class, but it's clunky for interactive editing:
export function parseURL(str) {
try {
const u = new URL(str);
return {
protocol: u.protocol,
username: u.username,
password: u.password,
host: u.hostname,
port: u.port,
pathname: u.pathname,
search: u.search,
hash: u.hash,
queryParams: parseQuery(u.search),
};
} catch {
return null;
}
}
The parsed object is flat and easy to edit. The queryParams key is an array of pairs instead of an object — that's important, because objects can't represent repeated keys (?tag=a&tag=b), which are valid URL syntax.
Query params as ordered pairs
export function parseQuery(search) {
if (!search || search === '?') return [];
const str = search.startsWith('?') ? search.slice(1) : search;
return str.split('&').map(pair => {
const [key, ...rest] = pair.split('=');
return [decodeComponent(key), decodeComponent(rest.join('='))];
});
}
export function buildQuery(pairs) {
if (pairs.length === 0) return '';
return '?' + pairs.map(([k, v]) =>
`${encodeComponent(k)}=${encodeComponent(v)}`
).join('&');
}
Pair format means:
- Order is preserved (some APIs are order-sensitive)
- Duplicate keys are supported
- Empty values stay empty (
?foo=is different from?foo) - Adding or removing a specific occurrence is straightforward
The add/update/remove operations
export function addParam(pairs, key, value) {
return [...pairs, [key, value]];
}
export function removeParam(pairs, index) {
return pairs.filter((_, i) => i !== index);
}
export function updateParam(pairs, index, key, value) {
return pairs.map((pair, i) => i === index ? [key, value] : pair);
}
All immutable. Each UI action produces a new pairs array, which gets passed back into buildQuery to regenerate the search string, which combines with the other components via buildURL.
Port 443 gotcha
While writing tests, I discovered: the native URL API silently strips default ports. new URL('https://example.com:443/') gives you port: '' because 443 is the default for HTTPS. Same for 80 on HTTP, 21 on FTP, 22 on SSH.
// Tests use port 9000 instead of 443 to avoid this behavior
const parsed = parseURL('https://example.com:9000/path');
assert.strictEqual(parsed.port, '9000');
If you need to preserve default ports literally, you'd have to detect them and carry them separately — the URL API doesn't let you.
IPv6 host handling
Another gotcha: URL.hostname for an IPv6 URL returns the address without brackets, but you need brackets when rebuilding the URL:
Input: https://[::1]:8080/
hostname: "[::1]" (actually returns with brackets in some implementations)
The implementation varies by browser/Node version. The safe check is hostname.includes('::1') rather than exact match.
Encode vs encodeURIComponent
JavaScript has three URL-ish encoding functions:
-
encodeURI()— escapes only characters that would break URL structure (?,#, space). Preserves:,/,@, etc. -
encodeURIComponent()— escapes everything that isn'tA-Za-z0-9-_.!~*'() -
escape()— deprecated, don't use
For URL components (query values, path segments), use encodeURIComponent. For whole URLs, use encodeURI (but it doesn't re-encode already-encoded chars, so round-tripping can be lossy).
Series
This is entry #92 in my 100+ public portfolio series.
- 📦 Repo: https://github.com/sen-ltd/url-parser
- 🌐 Live: https://sen.ltd/portfolio/url-parser/
- 🏢 Company: https://sen.ltd/

Top comments (0)