Scripting
Run JavaScript on matching requests and responses. Edit headers, query and JSON bodies, answer locally, keep state and call other services.
When a fixed rule is not enough, write a script. A script rule has a host and a path, and runs JavaScript in onRequest, onResponse or both for every matching exchange. Scripts run inside the WireLens app.
Add a script#
- Mac: open Tools ▸ Scripting and click Add Script. Give it a Name, a Host and a Path, and pick Run on: Request, Response or both. Each phase gets its own editor,
onRequestandonResponse. - iPhone and iPad: open Tools ▸ Scripting and add a script with a name, a Host pattern and a Path pattern, then turn on Run on requests, Run on responses or both.
- Android: long-press a host or a request and choose Script…, or open Tools ▸ Scripting and tap New script. Pick a template under Start from, then the Match and Run on: Requests, Responses or Both.
A rule with a blank pattern matches nothing. Each script has its own switch: turn it off to park the script without deleting it.
On the Mac and iPhone, only the first enabled script that matches a request runs. On the Mac, Move Up and Move Down in a rule's menu change the order, as does move_rule over MCP. On Android, every matching script runs, top to bottom.
A script that throws leaves the exchange as it was, and so does one that misses its deadline, unless a Mac rule's On timeout says Abort. The error shows in the run log: Runs on the Mac, Recent Runs on iPhone and iPad, Settings ▸ Debug Log on Android.
For HTTPS, the host has to be decrypted before a script can see it. See SSL Proxying.
The two hooks#
This is the API on the Mac, iPhone and iPad:
// Before the request leaves for the server.
function onRequest(req) {
req.headers.upsert({ key: 'X-WireLens', value: 'scripted' });
}
// After the server answers, before the app sees it.
function onResponse(req, res) {
var data = res.body.json();
if (data && typeof data === 'object') {
data.ok = true;
res.body.text = data;
}
}
Either hook can be async. WireLens waits for it, including $http calls, up to the rule's deadline.
Reading and changing an exchange#
| In a script | What it does |
|---|---|
req.method | The method. Assign to change it: req.. |
req.url | The full URL. Assign a new URL to send the request somewhere else. |
req.originalUrl | The URL before any rule changed it. |
req., .all() | Read headers. |
req. | Add a header, or replace it if it is already there. |
req. | Remove a header. |
req.query.get(name), .all() | Read query parameters. |
req., .remove(name) | Set or remove a query parameter. |
req.body.text | The body as text. Assign a string, or an object to write JSON. |
req.body.json() | The body parsed as JSON. |
req.body.multipart | The parts of a multipart form body, by name or index. |
req.bodyBytes | The body as a Uint8Array, for binary bodies. |
req.comment, req.color | The flow's comment and colour in the traffic list. |
res.status | The status. Assign to change it: res.. |
res.headers, res.body, res.bodyBytes | The response, the same way as the request's. |
Built-in functions#
| Function | What it does |
|---|---|
mock(status, headers, body) | Answers locally, without contacting the server. An object body is sent as JSON. |
abort() | Closes the connection without contacting the server. |
comment(text) | Adds a comment to the flow, which shows in the traffic list. |
console.log, console.warn, console.error | Write to the run log. |
readFile(path), writeToFile(path, data) | Read and write files of up to 1 MB in the script folder. |
On the Mac the script folder is ~/.
function onRequest(req) {
mock(503, { 'Content-Type': 'application/ json' }, {
error: 'service_unavailable',
message: 'Mocked by WireLens'
});
}
State, environment and HTTP#
$stateis a small store shared by every run in this app session:$state.get(key),$state.,set(key, value) $state.remove(key)and$state.all(). Values are strings. Use it to count requests, carry a token from one response into the next request, or fail every third call.$envholds values you would rather keep out of the script, such as API keys, with the same four functions. Add them under Environment on the Mac, or Environment Variables on iPhone and iPad. Every$envvalue is hidden from the run log and from error messages.$http(options)calls another service and returns a promise. Passurl, and optionallymethod,headers,bodyandtimeout. The result hasstatus,statusText,headers,body,urlanddurationMs.
async function onRequest(req) {
var count = Number($state.get('checkout') || 0) + 1;
$state.set('checkout', String(count));
if (count % 3 === 0) {
mock(502, { 'Content-Type': 'application/ json' }, { error: 'flaky upstream' });
return;
}
var token = await $http({ url: 'https:// auth.example.com/ token', method: 'POST' });
req.headers.upsert({ key: 'Authorization', value: 'Bearer ' + token.body });
}
On the Mac#
Each rule card has:
- Deadline: how long one run may take, in seconds. Blank uses 8.
- On timeout: Execute unchanged or Abort when a run misses the deadline.
- Conditions, folders, and Test this rule for sample URLs.
- Source: Insert Snippet, Completions (a cheatsheet of
req.,res.,$utils.and$addons.), and Open in VS Code or Open in Cursor when either is installed.
Insert Snippet has five working examples: Add request header, Rewrite a JSON field, Mock a 503 JSON body, Strip Cookie and Authorization and Drop with abort().
The Runs log lists every run with its URL, time, and anything it logged or failed on. Filter it to All, Warnings or Errors.
Helpers and packages are Mac only:
$utils(also$_) has Base64, hex, MD5, SHA-1, SHA-256, SHA-512, UUID, JSON minify and beautify, and JWT decode. Completions in a rule's Source row lists them. WireLens bundles$_and$utilsonly. lodash and crypto-js are not included.requireloads built-in addons such asrequire("uuid"). A// @require uuidline at the top of a script does the same.- Custom Addons are
.jsfiles in~/. Add one in the pane or drop a file there, then load it withLibrary/ Application Support/ WireLens/ addons/ custom/ require("name")orrequire("@custom/. A blank name orname") *is refused. - Shared libraries go in
~/.Library/ Application Support/ WireLens/ addons/ libs/ require("@libs/foo")loadsaddons/libs/foo.js. - npm Packages: Install runs
npm install <name>into~/.Library/ Application Support/ WireLens/ addons/ npm/ require("pkg")then loads the package'smainfile orindex.js. Packages with native modules do not load.
Scripts from Proxyman#
Proxyman scripts run as they are. A handler written as onRequest(context, url, request) or onResponse(context, url, request, response) receives Proxyman's objects: request.headers, request.queries, a parsed JSON body, response.statusCode, sharedState. Return the object, as in Proxyman, and WireLens applies what changed. File ▸ Import ▸ Proxyman Rules… brings your scripts across with their hosts, paths and Mock API setting.
On iPhone and iPad#
Tools ▸ Scripting has your scripts, Environment Variables and Recent Runs. Each script has a Deadline; a run that misses it continues unmodified. Test Run runs a script against a captured exchange and shows what it would change, what it logged and any error.
Scripts run in the WireLens app, not in the capture tunnel. Keep the app running while scripted traffic passes through. Otherwise each matching exchange waits for its deadline and then continues unmodified.
On Android#
Android scripts use the same shape as Proxyman's, which the Mac and iPhone also run. Each hook gets plain objects and returns the one to send on:
function onRequest(context, url, request) {
request.headers["X-Debug"] = "1";
return request;
}
function onResponse(context, url, request, response) {
response.statusCode = 200;
response.headers["Content-Type"] = "application/ json";
response.body = JSON.stringify({ ok: true, items: [] });
return response;
}
requesthasmethod,url,headersandbody. Changingurlsends the request somewhere else.responsehasstatusCode,headersandbody. An object assigned tobodyis sent as JSON.headersis a plain object. Repeated headers arrive joined with,.abort()cancels the exchange.console.logwrites to Settings ▸ Debug Log.
The templates under Start from are Add a header, Mock JSON, Change the status, Block, Log the body and Blank. Each run has 8 seconds. $state, $env, $http and mock() are not available on Android.
Free and Pro#
The free plan runs one script on every platform. Pro removes the limit.
Command line and MCP#
The command line and the MCP server run on the Mac. wirelens ships inside the app and is not on your PATH until you link it once.
wirelens rules script --host api.example.com --path '/ v2/ *' \
--name 'Debug header' \
--on-request-file ./debug-header.js
--on-request and --on-response take the source inline instead of from a file.
The MCP tool add_script_rule takes a host and path plus on_request and on_response source. Give it either the whole function or only the statements inside it. Three more tools help an agent get the script right before it runs on live traffic. script_reference returns this API. generate_script builds a script from a list of edits and can replay a captured response as a mock. test_script runs a script against a captured request and reports what it would change, what it logged and any error.
If a fixed answer or a header change is all you need, Map Local and Rewrite are easier to review.
Something here is unclear, or wrong for your setup? Contact support or write to support@wirelens.app.