Skip to main content
Scramjet’s behavior is controlled through a comprehensive configuration system. This page explains all configuration options, feature flags, and how to modify settings at runtime.

Configuration structure

The ScramjetConfig interface defines all available options:
interface ScramjetConfig {
  prefix: string;                                    // URL prefix for routing
  globals: { /* ... */ };                           // Runtime global function names
  files: { wasm: string; all: string; sync: string; }; // Script file paths
  flags: ScramjetFlags;                             // Feature flags
  siteFlags: Record<string, Partial<ScramjetFlags>>; // Per-site flag overrides
  codec: { encode: string; decode: string; };       // URL codec functions
}

Initialization config

When creating a ScramjetController, you provide a ScramjetInitConfig which has slightly different types:
interface ScramjetInitConfig extends Omit<ScramjetConfig, "codec" | "flags"> {
  flags: Partial<ScramjetFlags>;  // Flags are optional
  codec: {
    encode: (url: string) => string;  // Actual functions, not strings
    decode: (url: string) => string;
  };
}
The codec functions are serialized to strings when stored in config. This allows the Service Worker (which runs in a separate context) to deserialize and execute them.

Core options

prefix

The URL path prefix that identifies proxied requests.
const scramjet = new ScramjetController({
  prefix: "/scramjet/",
});
  • Must start and end with /
  • Should be unique to avoid conflicts
  • Used by the Service Worker to route requests
Default: /scramjet/

files

Paths to Scramjet’s runtime files:
files: {
  wasm: "/scramjet.wasm.wasm",  // WASM rewriter module
  all: "/scramjet.all.js",       // Main client bundle
  sync: "/scramjet.sync.js",     // Synchronous XHR support
}
These files are:
  • Injected into HTML documents
  • Served by your web server
  • Generated during the build process
Make sure these files are publicly accessible at the specified paths.

codec

The URL encoding/decoding functions:
codec: {
  encode: (url: string) => string;
  decode: (url: string) => string;
}
Default codec:
codec: {
  encode: (url: string) => encodeURIComponent(url),
  decode: (url: string) => decodeURIComponent(url),
}
Custom codec example:
codec: {
  encode: (url: string) => btoa(url),
  decode: (url: string) => atob(url),
}
Use base64 encoding for cleaner-looking URLs, but be aware it can make debugging harder.

globals

Names of global runtime functions injected into proxied pages:
globals: {
  wrapfn: "$scramjet$wrap",
  wrappropertybase: "$scramjet__",
  wrappropertyfn: "$scramjet$prop",
  cleanrestfn: "$scramjet$clean",
  importfn: "$scramjet$import",
  rewritefn: "$scramjet$rewrite",
  metafn: "$scramjet$meta",
  setrealmfn: "$scramjet$setrealm",
  pushsourcemapfn: "$scramjet$pushsourcemap",
  trysetfn: "$scramjet$tryset",
  templocid: "$scramjet$temploc",
  tempunusedid: "$scramjet$tempunused",
}
Don’t change these unless you’re modifying Scramjet’s internals. The JS rewriter expects these exact names.

Feature flags

Flags control specific behaviors and features:
type ScramjetFlags = {
  serviceworkers: boolean;        // Enable nested Service Worker support
  syncxhr: boolean;               // Enable synchronous XMLHttpRequest rewriting
  strictRewrites: boolean;        // Enforce strict URL rewriting
  rewriterLogs: boolean;          // Log rewriter timing information
  captureErrors: boolean;         // Capture and log errors from rewriting
  cleanErrors: boolean;           // Clean Scramjet internals from stack traces
  scramitize: boolean;            // Enable experimental scramitize feature
  sourcemaps: boolean;            // Generate source maps for rewritten JS
  destructureRewrites: boolean;   // Use destructuring in JS rewrites
  interceptDownloads: boolean;    // Intercept file downloads
  allowInvalidJs: boolean;        // Allow malformed JavaScript
  allowFailedIntercepts: boolean; // Continue if API interception fails
};

Flag details

serviceworkers

Type: boolean | Default: falseEnables support for nested Service Workers within proxied pages. When enabled, sites can register their own Service Workers that will be emulated.
flags: {
  serviceworkers: true
}
This is experimental and may not work with all sites.
Type: boolean | Default: falseEnables synchronous XMLHttpRequest rewriting. When enabled, synchronous XHR calls are intercepted and proxied.
flags: {
  syncxhr: true
}
Synchronous XHR is deprecated in browsers but some legacy sites still use it.
Type: boolean | Default: trueWhen enabled, enforces strict URL rewriting. Invalid URLs will cause errors instead of being silently ignored.
Disable this for compatibility with sites that have malformed URLs.
Type: boolean | Default: falseLogs timing information for rewriting operations to the console:
[Scramjet] HTML rewrite: 45ms
[Scramjet] oxc rewrite for "app.js": 12ms
Enable this during development to identify performance bottlenecks.
Type: boolean | Default: trueCaptures errors that occur during rewriting and logs them. Prevents errors in Scramjet from breaking the proxied page.
Type: boolean | Default: falseRemoves Scramjet internals from error stack traces shown to the proxied page. Makes debugging easier for end users but harder for Scramjet developers.
Type: boolean | Default: falseExperimental feature for enhanced obfuscation.
This is experimental and may break compatibility.
Type: boolean | Default: trueGenerates source maps for rewritten JavaScript. Allows browser DevTools to show original code.
flags: {
  sourcemaps: true  // You can debug rewritten code
}
Type: boolean | Default: falseUses destructuring assignment in rewritten JavaScript for potential performance improvements.
Type: boolean | Default: falseIntercepts file downloads and dispatches them as events instead of triggering browser downloads:
flags: {
  interceptDownloads: true
}

scramjet.addEventListener("download", (event) => {
  console.log("Downloading:", event.download.filename);
  // Handle download programmatically
});
Type: boolean | Default: trueAllows malformed JavaScript to be served without rewriting. If the JS rewriter fails to parse code, the original code is returned.
Disabling this may break sites with syntax errors in their JavaScript.
Type: boolean | Default: trueIf API interception fails (e.g., due to browser quirks), continue instead of throwing an error.

Site-specific flags

You can override flags for specific sites using regular expressions:
const scramjet = new ScramjetController({
  prefix: "/scramjet/",
  flags: {
    interceptDownloads: false,  // Global default
  },
  siteFlags: {
    "example\\.com": {
      interceptDownloads: true,  // Override for example.com
    },
    "cdn\\..*": {
      sourcemaps: false,  // Disable sourcemaps for CDN resources
    },
  },
});
The flagEnabled() helper checks site-specific overrides:
import { flagEnabled } from "@/shared";

if (flagEnabled("interceptDownloads", url)) {
  // Handle download interception
}
Site flags are checked in order, and the first match wins. Be careful with overlapping patterns.

Runtime configuration

Modifying config

You can update configuration at runtime using modifyConfig():
await scramjet.modifyConfig({
  flags: {
    rewriterLogs: true,
  },
});
This:
  1. Updates the in-memory config
  2. Saves to IndexedDB
  3. Notifies the Service Worker via postMessage
  4. Reloads codec functions
Config changes only affect new requests. Existing pages won’t be affected until they reload.

Reading config

Access the current config via the global config variable:
import { config } from "@/shared";

console.log("Current prefix:", config.prefix);
console.log("Sourcemaps enabled?", config.flags.sourcemaps);

Loading config in Service Worker

The Service Worker loads config from IndexedDB:
const scramjet = new ScramjetServiceWorker();

// Config is loaded automatically on first fetch
self.addEventListener("fetch", (event) => {
  if (scramjet.route(event)) {
    event.respondWith(scramjet.fetch(event));
  }
});
Manual loading:
await scramjet.loadConfig();

Codec configuration

Loading codecs

Codec functions are serialized to strings in config and deserialized at runtime:
export function loadCodecs() {
  codecEncode = new Function(`return ${config.codec.encode}`)() as any;
  codecDecode = new Function(`return ${config.codec.decode}`)() as any;
}
This allows the codec to work in the Service Worker context, which doesn’t have access to the original function objects.

Using codecs

Use the global codec functions for encoding/decoding:
import { codecEncode, codecDecode } from "@/shared";

const encoded = codecEncode("https://example.com");
const decoded = codecDecode(encoded);

Default configuration

Here’s the complete default config:
const defaultConfig: ScramjetInitConfig = {
  prefix: "/scramjet/",
  globals: {
    wrapfn: "$scramjet$wrap",
    wrappropertybase: "$scramjet__",
    wrappropertyfn: "$scramjet$prop",
    cleanrestfn: "$scramjet$clean",
    importfn: "$scramjet$import",
    rewritefn: "$scramjet$rewrite",
    metafn: "$scramjet$meta",
    setrealmfn: "$scramjet$setrealm",
    pushsourcemapfn: "$scramjet$pushsourcemap",
    trysetfn: "$scramjet$tryset",
    templocid: "$scramjet$temploc",
    tempunusedid: "$scramjet$tempunused",
  },
  files: {
    wasm: "/scramjet.wasm.wasm",
    all: "/scramjet.all.js",
    sync: "/scramjet.sync.js",
  },
  flags: {
    serviceworkers: false,
    syncxhr: false,
    strictRewrites: true,
    rewriterLogs: false,
    captureErrors: true,
    cleanErrors: false,
    scramitize: false,
    sourcemaps: true,
    destructureRewrites: false,
    interceptDownloads: false,
    allowInvalidJs: true,
    allowFailedIntercepts: true,
  },
  siteFlags: {},
  codec: {
    encode: (url: string) => encodeURIComponent(url),
    decode: (url: string) => decodeURIComponent(url),
  },
};

Configuration persistence

Configuration is stored in IndexedDB under the $scramjet database:
const db = await openDB<ScramjetDB>("$scramjet", 1, {
  upgrade(db) {
    if (!db.objectStoreNames.contains("config")) {
      db.createObjectStore("config");
    }
    // ... other stores
  },
});

await db.put("config", config, "config");
This ensures:
  • Config persists across page reloads
  • Service Worker and clients use the same config
  • Runtime changes are preserved

Best practices

Start with default config and only change what you need. Most flags are optimized for compatibility.
Don’t modify globals unless you’re changing Scramjet’s internals. The rewriter depends on specific names.
Use site-specific flags to handle edge cases without affecting all sites:
siteFlags: {
  "problematic-site\\.com": {
    strictRewrites: false,
    allowInvalidJs: true,
  },
}

Debugging config issues

Enable logging to see config-related information:
scramjet.modifyConfig({
  flags: {
    rewriterLogs: true,
    captureErrors: true,
  },
});
Check the current config in DevTools:
// In the console
import("@/shared").then(m => console.log(m.config));

Next steps

Architecture

Understand how configuration flows through components

API Reference

See the full ScramjetController API

Build docs developers (and LLMs) love