Script performance and memory
Scripts are the gateway's most expressive but also most expensive rewriting approach. This page is the single authority on script performance and memory; the other pages (Upstream and model fields, Scripting API reference, Scripting configuration, Write your first script transform) only point back here.
Overhead comes from serialization
The script engine (QuickJS-based) receives the JSON body parsed into objects. The overhead mostly comes from JSON serialization/deserialization, whose latency grows linearly with body size.
Latency reference
| Body size | Typical single-script latency |
|---|---|
| < 10 KB | millisecond-level |
| ~100 KB | tens of milliseconds |
| ~1 MB (large multimodal conversation with inline base64) | rises significantly, and consumes memory |
Don't use scripts for simple rewriting
For simple field add/delete/modify, use request_payload declarative rules — they rewrite the request body directly inside the gateway without going through script-engine serialization, giving the best performance. For selection, see Request rewriting and routing: how to choose.
Anti-pattern: using after-slot scripts for pure routing decisions
Don't use after-slot scripts for pure routing decisions on large bodies
After-slot scripts read the entire body (including inline base64) into script-engine memory, and large multimodal conversations can easily trigger script-engine memory OOM; also, under script_error_mode = log-and-continue, the route switch is silently skipped.
For these "detect image/web access → switch model" pure routing decisions, as long as the target uses the same protocol as the current route, use native switch-route rules instead — their predicates only do structural/small-value checks and never read binary content bytes, disk spooling stays enabled for large request bodies, and a body containing base64 stays on disk as placeholders + a disk temporary file rather than being read into memory.
Only fall back to scripts when the switch is cross-protocol, or the check logic exceeds what "field existence/small-value equality" can express — and put it in the before slot to read the client's original format (the before slot runs before protocol translation and has not yet been parsed into the target-protocol body).
Writing advice
- Read only the fields you need: don't
JSON.stringify(body)the entire body; fetch fields on demand. - Return early:
return bodyas soon as the condition fails, to reduce unnecessary processing. - Avoid large object copies inside loops.
- Streaming response scripts: process per chunk and avoid accumulating state.
Choosing the error mode
log-and-continue(default): if the script fails, the original body passes through and the request is not interrupted. Good for non-critical rewriting.log-and-reject: if the script fails, the request is rejected outright. Use this when compliance requirements are strict (redaction must not leak).
See Scripting configuration for the fields.
FAQ
Q: What if the script times out?SCRIPT_MAX_OPERATIONS (default 2000) caps the maximum operations per script execution. Exceeding it aborts execution. Infinite loops or overly heavy logic are caught by this.
Q: A multimodal conversation script OOMs, but I genuinely need to read image content to decide? Reading image content to decide inherently loads base64 into memory. Consider turning the check into a structural check (use the switch-route when predicate to check "is there an image block" rather than reading image content), or divert traffic with a protocol override at the upstream/model level.
Q: Both before and after slots have scripts configured — what's the execution order? Before runs first (and can trigger an immediate useModel switch), after runs later. Once before triggers a switch, the original route's after does not execute. See Scripting API reference.
Next: Request rewriting and routing: how to choose for selection; Scripting API reference for the API; Scripting configuration for configuration.
