Skip to main content
Scramjet uses URL codecs to encode real URLs into proxy URLs and decode them back. You can customize this behavior by providing your own encoding and decoding functions.

Default codec

By default, Scramjet uses simple URL encoding:
// Default encoder
function encode(url) {
  if (!url) return url;
  return encodeURIComponent(url);
}

// Default decoder
function decode(url) {
  if (!url) return url;
  return decodeURIComponent(url);
}
With the default codec and prefix /scramjet/, a URL like https://example.com becomes:
/scramjet/https%3A%2F%2Fexample.com

Creating a custom codec

Custom codecs are defined when creating the ScramjetController:
const { ScramjetController } = $scramjetLoadController();

const scramjet = new ScramjetController({
  prefix: '/scramjet/',
  codec: {
    encode: (url) => {
      // Your custom encoding logic
      return btoa(url);
    },
    decode: (url) => {
      // Your custom decoding logic
      return atob(url);
    },
  },
});

await scramjet.init();
The encoder and decoder functions must be inverse operations - what you encode must be decodable back to the original URL.

Common codec implementations

Base64 encoding

Base64 encoding makes URLs more obfuscated:
const scramjet = new ScramjetController({
  codec: {
    encode: (url) => {
      if (!url) return url;
      return btoa(url);
    },
    decode: (url) => {
      if (!url) return url;
      return atob(url);
    },
  },
});

XOR cipher encoding

Add simple obfuscation with an XOR cipher:
function xorCipher(str, key) {
  let result = '';
  for (let i = 0; i < str.length; i++) {
    result += String.fromCharCode(
      str.charCodeAt(i) ^ key.charCodeAt(i % key.length)
    );
  }
  return result;
}

const scramjet = new ScramjetController({
  codec: {
    encode: (url) => {
      if (!url) return url;
      const key = 'mySecretKey';
      const encrypted = xorCipher(url, key);
      return btoa(encrypted)
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
    },
    decode: (url) => {
      if (!url) return url;
      const key = 'mySecretKey';
      // Add padding
      let base64 = url
        .replace(/-/g, '+')
        .replace(/_/g, '/');
      while (base64.length % 4) {
        base64 += '=';
      }
      const encrypted = atob(base64);
      return xorCipher(encrypted, key);
    },
  },
});
XOR cipher provides minimal security and should not be relied upon for actual encryption. Use it only for obfuscation.

Plain encoding

For maximum compatibility and debugging, use plain text URLs:
const scramjet = new ScramjetController({
  codec: {
    encode: (url) => url,
    decode: (url) => url,
  },
});
Plain encoding requires special web server configuration to handle URLs containing :// and other special characters.

Custom delimiter encoding

Use a custom delimiter instead of URL encoding:
const scramjet = new ScramjetController({
  codec: {
    encode: (url) => {
      if (!url) return url;
      // Replace :// with a custom delimiter
      return url.replace('://', '---');
    },
    decode: (url) => {
      if (!url) return url;
      // Restore ://
      return url.replace('---', '://');
    },
  },
});

// https://example.com becomes /scramjet/https---example.com

Hash fragment handling

Scramjet automatically handles URL hash fragments separately:
// Input URL: https://example.com/page#section
// The codec receives: https://example.com/page
// The hash is encoded separately: #section
Your codec doesn’t need to handle hash fragments - Scramjet manages them automatically:
const url = new URL('https://example.com/page#section');

// Scramjet encodes the hash separately
const encodedHash = codecEncode(url.hash.slice(1));
const realHash = encodedHash ? '#' + encodedHash : '';

// The URL is encoded without the hash
url.hash = '';
const encodedUrl = config.prefix + codecEncode(url.href) + realHash;

Using the controller’s encode/decode methods

The ScramjetController provides helper methods for encoding and decoding URLs:
const { ScramjetController } = $scramjetLoadController();

const scramjet = new ScramjetController({
  prefix: '/scramjet/',
  codec: {
    encode: (url) => btoa(url),
    decode: (url) => atob(url),
  },
});

await scramjet.init();

// Encode a URL
const proxiedUrl = scramjet.encodeUrl('https://example.com');
console.log(proxiedUrl); // /scramjet/aHR0cHM6Ly9leGFtcGxlLmNvbQ

// Decode a URL
const realUrl = scramjet.decodeUrl(proxiedUrl);
console.log(realUrl); // https://example.com
Use these methods instead of calling your codec functions directly to ensure proper prefix handling.

Protocol handling

Scramjet only proxies HTTP and HTTPS URLs. Other protocols are passed through unchanged:
const scramjet = new ScramjetController({
  codec: {
    encode: (url) => btoa(url),
    decode: (url) => atob(url),
  },
});

await scramjet.init();

// HTTP/HTTPS URLs are encoded
scramjet.encodeUrl('https://example.com');
// -> /scramjet/aHR0cHM6Ly9leGFtcGxlLmNvbQ

// Other protocols pass through
scramjet.encodeUrl('mailto:test@example.com');
// -> mailto:test@example.com

scramjet.encodeUrl('javascript:alert(1)');
// -> javascript:alert(1)

Testing your codec

Here’s a test suite for validating your custom codec:
function testCodec(encoder, decoder) {
  const testUrls = [
    'https://example.com',
    'https://example.com/path?query=value',
    'https://example.com:8080/path',
    'http://subdomain.example.com/path',
    'https://example.com/path?q=hello world&lang=en',
  ];

  console.log('Testing codec...');

  for (const url of testUrls) {
    const encoded = encoder(url);
    const decoded = decoder(encoded);

    if (url === decoded) {
      console.log('✓', url);
    } else {
      console.error('✗', url);
      console.error('  Expected:', url);
      console.error('  Got:', decoded);
    }
  }
}

// Test your codec
testCodec(
  (url) => btoa(url),
  (url) => atob(url)
);

Performance considerations

The codec functions are called frequently during page loads. Keep them fast:
// Fast - simple operations
encode: (url) => btoa(url)

// Slower - complex operations
encode: (url) => {
  // Multiple regex operations
  return url
    .replace(/:/g, '%3A')
    .replace(/\//g, '%2F')
    .replace(/\?/g, '%3F')
    // ... many more replacements
}
Some codecs increase URL length significantly:
const url = 'https://example.com';

// URL encoding (default): 25 chars
encodeURIComponent(url); // https%3A%2F%2Fexample.com

// Base64: 28 chars
btoa(url); // aHR0cHM6Ly9leGFtcGxlLmNvbQ==

// XOR + Base64: 28+ chars
Long URLs may cause issues with some servers or browsers.
Always handle edge cases:
const scramjet = new ScramjetController({
  codec: {
    encode: (url) => {
      if (!url) return url;
      try {
        return btoa(url);
      } catch (error) {
        console.error('Encoding failed:', error);
        // Fallback to default encoding
        return encodeURIComponent(url);
      }
    },
    decode: (url) => {
      if (!url) return url;
      try {
        return atob(url);
      } catch (error) {
        console.error('Decoding failed:', error);
        // Fallback to default decoding
        return decodeURIComponent(url);
      }
    },
  },
});

Basic setup

Learn how to configure Scramjet

Configuration flags

Explore other configuration options