Skip to content

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 →