HTTPS Capture ManualCapture Scripts
HTTPS Capture Scripts: JavaScript Hooks for Requests and Responses
The complete reference for HTTPS Capture's JavaScript rule scripts: script matching, the five hook points, the request/response APIs, header and query bags, shared state, persistent environment variables, limits and safety rules.
When rewrite rules are too rigid for the job, Scripts give you a real programming language inside the capture engine. A script is a plain JavaScript file that HTTPS Capture runs at five well-defined points of each request/response exchange — you can inspect and rewrite URLs, headers, queries and bodies, share state across requests, keep counters in persistent storage, and block traffic outright.
Manage scripts at Rules → Scripts. Each script has an enable toggle; drag to reorder — the first enabled script whose match applies handles a request exclusively, so order is priority. The built-in editor provides syntax highlighting, and you can import .capture.js files, share scripts out via the share button, or start from one of the bundled examples. The AI Prompt page copies a complete API specification you can paste into any AI assistant to generate a valid script; Runtime Logs shows console output from running scripts; Script Env lists persistent environment variables.
A first script
Capture.defineScript({
name: "Debug Header",
version: "1.0.0",
description: "Adds a debug header to API requests.",
match: {
urls: ["https://api.example.com/*"],
excludeUrls: [],
methods: []
},
onRequestHeaders(context, request) {
request.headers.set("X-Debug", "1");
return request;
}
});
A file must call Capture.defineScript({...}) exactly once, and the editor validates it before saving: the name must be set, match.urls must contain at least one non-empty entry, and the source must parse. Everything else — hooks, description, version — is optional.
Matching
match.urls— required. Each entry is a glob with*wildcards, matched against the full request URL.match.excludeUrls— takes precedence overurls.match.methods— case-insensitive list (["GET", "POST"]); an empty array matches every method.shouldMatch(request)— optional function hook that runs after static matching for final say, e.g. skip health checks withreturn request.path !== "/health".
Only the first enabled script whose match applies runs for a request; other scripts do not see it. Reordering the list changes which script wins.
The five hooks
Include only the hooks you need; all of them may be async.
| Hook | Runs when | Typical use |
|---|---|---|
shouldMatch(request) | After static URL/method matching, before any other hook | Fine-grained matching decisions |
onRequestHeaders(context, request) | Request headers parsed, before the upstream connection opens | Rewrite URL, host, path, queries, headers |
onRequest(context, request) | Request body fully received | Inspect or replace the request body |
onResponseHeaders(context, request, response) | Response headers received, before they are delivered to the client | Rewrite response headers and status |
onResponse(context, request, response) | Response body fully buffered | Inspect or replace the response body |
Every hook returns the (possibly modified) request or response object. Returning null from a request hook (onRequestHeaders or onRequest) blocks the request — the connection is dropped before anything reaches the server.
Request API
request.id // Capture record ID
request.originalUrl // Original absolute URL; treat as read-only
request.url // Absolute HTTP/HTTPS URL, readable and writable
request.method // HTTP method, readable and writable
request.host // Hostname, readable and writable
request.port // Number or null, readable and writable
request.path // URL path, readable and writable
request.queries // Ordered query bag (see below)
request.headers // Ordered, case-insensitive header bag (see below)
request.body // UTF-8 string or null; assigning replaces body bytes
request.bodyBytes // Uint8Array copy; assigning replaces body bytes
To change routing, assign a valid absolute request.url, or update host, port and path individually — the connection is re-resolved after the header hooks. Query mutations rebuild request.url automatically.
Response API
response.statusCode // Integer from 100 through 599
response.headers // Ordered, case-insensitive header bag
response.body // UTF-8 string or null; assigning replaces body bytes
response.bodyBytes // Uint8Array copy; assigning replaces body bytes
After a body change, HTTPS Capture recalculates body framing (Content-Length, removes stale Content-Encoding) for you. Set Content-Type yourself when you change the representation format.
Header and query bags
Both bags preserve duplicate values (several Set-Cookie headers, repeated query parameters) and support the same operations:
bag.get(name) // First value or undefined
bag.getAll(name) // All values as an array
bag.set(name, value) // Replace all values with one value
bag.append(name, value)
bag.delete(name)
bag.toJSON() // Array of { name, value } pairs
Property syntax works too:
request.headers["X-Debug"] = "1";
delete request.headers["X-Remove"];
request.queries["mode"] = "debug";
Header names are case-insensitive; query names are case-sensitive. Use append when duplicates must be preserved.
State: per-flow and per-session
context.state // JSON object shared between hooks for this request/response flow
context.shared // JSON object shared across all flows this script handles, cleared when capture stops
A typical pattern: stamp a start time in onRequest, compute the elapsed milliseconds in onResponse. Keep values JSON-compatible (null, booleans, numbers, strings, arrays, plain objects) and small — each object is limited to 64 KB. Never store credentials or body payloads in shared state.
Persistent environment variables
env.get(key) // Stored string, or null when missing
env.set(key, value) // Persists a string across runs, scripts and app restarts
env survives capture restarts and is shared with the JavaScript requests feature in the Requests tab; browse stored values under Rules → Scripts → Script Env. Use it for tokens or counters that must outlive a single flow. Limits: keys 1–128 characters, values up to 4,096 characters, at most 100 keys / 256 KB total. Reads inside a hook see a snapshot taken when the hook started; writes made before an exception still persist.
Built-in utilities and logging
Capture.crypto.md5(text)
Capture.crypto.sha256(text)
Capture.base64.encode(text)
Capture.base64.decode(base64Text)
console.log(...values)
console.info(...values)
console.warn(...values)
console.error(...values)
Console output lands in Rules → Scripts → Runtime Logs — bounded to 100 entries per execution and 2,048 characters each, so keep messages short and never log authorization values, cookies or tokens.
Limits and safety rules
- Script source is limited to 256 KB.
- Body hooks (
onRequest,onResponse) process at most 4 MB; larger bodies and streaming responses (Server-Sent Events) skip body hooks. - Time budgets: matching 50 ms, header hooks 100 ms, body hooks 1 second. Overrunning kills and rebuilds the runtime.
- Requests must stay absolute HTTP/HTTPS URLs with ports 1–65535; header names must be valid HTTP tokens and values must not contain NUL, CR or LF — invalid output is rejected, protecting both the client connection and the upstream.
- There is no network, file or process access:
fetch,XMLHttpRequest,WebSocketandEventSourceare removed from the runtime, and scripts run in an isolated content world. - Scripts fail open: if a script throws or times out, the traffic continues with the last valid data and an error entry appears in Runtime Logs. A buggy script can slow debugging but cannot take your device offline.
Prefer header hooks when you do not need body access, avoid unbounded loops and repeated JSON.parse, and preserve duplicate headers and queries unless replacement is the point.
A larger example
Capture.defineScript({
name: "Block Ads, Time API",
version: "1.0.0",
description: "Blocks ad hosts and injects elapsed time into API responses.",
match: {
urls: ["*://*/*"],
excludeUrls: [],
methods: []
},
onRequestHeaders(context, request) {
if (request.host.endsWith("ads.example.com")) return null; // block
return request;
},
onRequest(context, request) {
context.state.startedAt = Date.now();
return request;
},
onResponse(context, request, response) {
if (request.headers.get("X-Debug") === "1" && response.body) {
try {
const body = JSON.parse(response.body);
body._elapsedMs = Date.now() - (context.state.startedAt || Date.now());
response.body = JSON.stringify(body);
} catch (e) {
console.warn("non-JSON body skipped");
}
}
response.headers.delete("X-Internal-Debug");
return response;
}
});
Where to go next
- Rules — the rule types that surround scripts: rewrite, mock, routing, replay, breakpoints and network simulation.
- Quick Start — capturing your first session with the local VPN and CA certificate.