URL Encode / Decode — JavaScript source
Percent-encode or decode URLs and query parameters. Choose component (encodeURIComponent) or full-URI (encodeURI) mode. 100% client-side.
This is the JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
/**
* URL encode / decode - component-level (encodeURIComponent) and full URI (encodeURI).
*
* Language: JavaScript
* CosmoDev polyglot showcase port of the `url-encode` tool.
* Ported from src/tools/UrlEncodeTool.tsx - display source, part of CosmoDev's
* polyglot tool pages.
*
* Explicit percent-encoding matching the platform built-ins. Each non-safe
* byte of the input's UTF-8 encoding is rendered as %XX (uppercase hex).
* Component scope leaves A-Za-z0-9-_.!~*'() unescaped; full URI scope also
* leaves the reserved set ;,/?:@&=+$# so whole URLs stay navigable. Decode
* reverses this, throwing a URIError on malformed % sequences; for full URI,
* encoded reserved characters are left intact. UTF-8 codec via the standard
* TextEncoder / TextDecoder (fatal, so invalid UTF-8 throws like decodeURIComponent).
*/
const TEXT_ENCODER = new TextEncoder();
const TEXT_DECODER = new TextDecoder('utf-8', { fatal: true });
// Characters encodeURIComponent never escapes.
const COMPONENT_SAFE = new Set(
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.!~*\'()',
);
// Characters encodeURI additionally leaves unescaped.
const URI_EXTRA = new Set(';/?:@&=+$#,');
const RESERVED_BYTES = new Set(Array.from(';/?:@&=+$#', (c) => c.charCodeAt(0)));
const HEX = '0123456789ABCDEF';
const isHex = (c) =>
(c >= 0x30 && c <= 0x39) || (c >= 0x41 && c <= 0x46) || (c >= 0x61 && c <= 0x66);
const hexVal = (c) => (c <= 0x39 ? c - 0x30 : (c & 0x5f) - 0x41 + 10);
/**
* Percent-encode `input`. `fullUri` selects encodeURI vs encodeURIComponent.
*/
export function encode(input, fullUri = false) {
const bytes = TEXT_ENCODER.encode(input);
let out = '';
for (const b of bytes) {
const ch = String.fromCharCode(b);
if (COMPONENT_SAFE.has(ch) || (fullUri && URI_EXTRA.has(ch))) {
out += ch;
} else {
out += '%' + HEX[b >> 4] + HEX[b & 0x0f];
}
}
return out;
}
/**
* Percent-decode `input`. Throws a URIError on malformed sequences.
* `fullUri` selects decodeURI (encoded reserved chars preserved) vs decodeURIComponent.
*/
export function decode(input, fullUri = false) {
let out = '';
let i = 0;
while (i < input.length) {
if (input.charCodeAt(i) !== 0x25) {
out += input[i];
i++;
continue;
}
// '%' must be followed by two hex digits.
const b1 = readHexByte(input, i);
if (b1 < 0) throw new URIError('URI malformed');
if (b1 < 0x80) {
if (fullUri && RESERVED_BYTES.has(b1)) {
out += input.slice(i, i + 3); // keep the encoded reserved byte as-is
} else {
out += String.fromCharCode(b1);
}
i += 3;
continue;
}
// Multi-byte UTF-8 lead byte: collect continuation bytes.
let len;
if ((b1 & 0xe0) === 0xc0) len = 2;
else if ((b1 & 0xf0) === 0xe0) len = 3;
else if ((b1 & 0xf8) === 0xf0) len = 4;
else throw new URIError('URI malformed');
const buf = [b1];
for (let n = 1; n < len; n++) {
const j = i + 3 * n;
if (j >= input.length || input.charCodeAt(j) !== 0x25) throw new URIError('URI malformed');
const cb = readHexByte(input, j);
if (cb < 0 || (cb & 0xc0) !== 0x80) throw new URIError('URI malformed');
buf.push(cb);
}
out += TEXT_DECODER.decode(Uint8Array.from(buf));
i += 3 * len;
}
return out;
}
// Read the two hex digits after the '%' at index `i`. Returns -1 on malformed input.
function readHexByte(s, i) {
if (i + 2 >= s.length) return -1;
const hi = input_hex(s.charCodeAt(i + 1));
const lo = input_hex(s.charCodeAt(i + 2));
if (hi < 0 || lo < 0) return -1;
return (hi << 4) | lo;
}
function input_hex(c) {
return isHex(c) ? hexVal(c) : -1;
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →