Scripting API reference
GateLLM ships a JavaScript script engine (QuickJS-based) for custom transforms on request and response JSON bodies. Scripts run in a sandbox with strict resource limits — safe and controllable.
This chapter is the API reference. For an intro see Write your first script transform; for config see Scripting config.
Basic structure
Each script defines a transform function that receives body and context, and must return body:
function transform(body, context) {
// read and modify body
// read context for metadata (read-only)
return body; // required!
}| Parameter | Mutable | Description |
|---|---|---|
body | yes | JSON request body or response body |
context | no | Request/response metadata (model name, headers, etc.) |
The Context object
context provides metadata of the current request/response, read-only:
Available in both request and response phases
| Field | Type | Description |
|---|---|---|
context.clientModel | string | The model name the client sees |
context.upstreamModel | string | The model ID sent to the upstream |
context.upstreamName | string | The upstream group name |
context.sourceProtocol | string | The client protocol (e.g. "OpenAIChatCompletions") |
context.targetProtocol | string | The upstream protocol (e.g. "Anthropic") |
context.stream | boolean | Whether it's a streaming request |
context.headers | object | Request headers (sensitive headers filtered out) |
context.query | object | URL query parameters |
context.requestId | string | Unique request ID |
context.clientIp | string | Client IP |
context.accessKeyName | string | Access key name |
context.accessKeyGroup | string | Access key group |
Available in the response phase only
| Field | Type | Description |
|---|---|---|
context.responseStatus | number | Upstream HTTP status code |
context.responseHeaders | object | Upstream response headers |
context.elapsedMs | number | Elapsed time (milliseconds) |
Security: sensitive headers like
authorization,cookie,x-api-keyare removed before scripts can see them.
context.useModel route switching
context.useModel("model-name");Full route switch: base_url, protocol, and credentials change together. body.model = "X" only changes the request body's field, not the route — use context.useModel to switch across endpoints/protocols.
The two request-script slots (before / after)
Request-side scripts are no longer one script plus a position switch, but two independent slots, each optionally empty and configurable together:
| Slot | Field | Run timing | Body seen | useModel capability |
|---|---|---|---|---|
| After translation | request_transform_after | after protocol translation | upstream protocol format | same-protocol switch only |
| Before translation | request_transform_before | before protocol translation | client original format | supports cross-protocol switch |
request_transform_after: runs after protocol translation, the body is already in upstream format. Suitable for simple field edits and upstream-dialect rewrites.request_transform_before: runs before protocol translation, the body is the client's original format. Suitable for detecting client input (images, search, plan-mode markers) and switching routes.- Both slots can be configured together: before and after of the same route merge independently (model overrides upstream), and both scripts run — before runs first (can trigger a cross-protocol immediate
useModelswitch), after runs later. Note: once the before slot triggers an immediateuseModelroute switch, the original route's after slot no longer runs (the whole pipeline switches to the target route's); the after slot's declareduseModelis a same-protocol deferred switch.response_transformhas no slot concept and stays a single field.
Execution order
The before and after slots each run independently; when both are configured for the same route, before runs first in the pre-translation phase and after in the post-translation phase. Below are the positions of the two slots in the pipeline.
Request side — after slot (post-translation)
client request → protocol translation → auto normalization → request_payload rules → upstream script → model script → cache injection → send to upstreamRequest side — before slot (pre-translation)
client request → upstream script → model script → (useModel swap) → protocol translation → auto normalization → request_payload rules → cache injection → send to upstreamResponse side (non-streaming only)
upstream response → response script → reverse protocol translation → return to clientResponse scripts for streaming requests are not supported yet.
Built-in functions
JSON Path operations
// read a nested value
const temp = json_path(body, "generation_config.temperature");
// set a value (auto-creates intermediate objects)
set_path(body, "generation_config.max_output_tokens", 4096);
// remove a field
remove_path(body, "deprecated_field");
// add only when the field doesn't exist
add_if_absent_path(body, "temperature", 1.0);
// append to an array
append_path(body, "messages", { role: "system", content: "Be concise." });String functions
str_len("Hello, 世界!"); // 9 (character count, not bytes)
str_contains("hello world", "world"); // true
str_replace("foo-bar", "-", "_"); // "foo_bar"
str_starts_with("hello", "he"); // true
str_trim(" hello "); // "hello"Regular expressions
regex_match("abc123", "[0-9]+"); // true
regex_replace("foo123bar", "[0-9]+", "N"); // "fooNbar"
regex_extract("a=1 b=2", "([a-z]+)="); // ["a", "b"]Encoding and hashing
base64_encode("hello"); // "aGVsbG8="
base64_decode("aGVsbG8="); // "hello"
sha256("hello"); // "2cf24dba5fb0..."
json_encode(body.messages); // serialize to string
json_parse(jsonStr); // parse from stringArray functions
array_sort([3, 1, 2]); // [1, 2, 3]
array_unique([1, 2, 2, 3]); // [1, 2, 3]
array_filter_by(items, "active", true); // keep elements with active=true
array_find(users, "name", "alice"); // find the first element with name=alice
array_pluck(users, "name"); // extract all name fields
array_sum([1.5, 2.5, 3.0]); // 7.0Deep copy and merge
const backup = deep_clone(body); // independent copy
const merged = deep_merge(defaults, overrides); // recursive mergeUUID and time
uuid(); // "550e8400-e29b-41d4-a716-..."
now(); // 1748620800 (second-level timestamp)
now_ms(); // millisecond-level
format_time(now(), "%Y-%m-%d %H:%M:%S"); // "2025-05-31 00:00:00"Runtime limits, error handling, and testing
The sandbox capability boundaries (what scripts can/can't do), the resource-limit table (operation count, heap cap, concurrent execution slots, lazy-projection semantics), script_error_mode error handling, common errors and troubleshooting, the test endpoint (POST /console/api/scripts/test), and performance advice are all in Script runtime limits and configuration — that is the single authoritative page for script config and limits; this page does not repeat them.
FAQ
Q: The script changed body.model, but the route didn't change.body.model only changes the request body's field, not the route. To switch routes use context.useModel("model-name").
Q: Does a response script work for streaming requests? Streaming-response scripts are not supported yet. Request scripts work for streaming requests, but response scripts only take effect for non-streaming.
Q: How do I choose between Before and After script positions?
- Only changing fields of the upstream-format body → After (default)
- Needing to detect the client's original format (e.g. different protocols have different image fields) and the judgment exceeds what
switch-routecan express → Before - Pure routing decisions like "detect image/web-search → switch model" (including cross-protocol) → prefer the native
switch-routerule, see Route image-bearing requests to a vision model
Next: Write your first script transform for the intro structure; Fix protocol translation with scripts and Redact responses with a script for real examples; Script runtime limits and configuration for the sandbox, resource limits, error handling, and testing.
