Skip to content

Interceptors

Interceptors are JavaScript modules that hook into the request/response lifecycle. They let you add authentication, transform requests and responses, inject headers, and short-circuit requests with custom responses.

Working example: see examples/interceptors/

An interceptor is a JavaScript file that exports a function. The function receives request or response data and returns an action:

interceptors/add-header.js
exports.onRequest = function onRequest(request) {
return {
action: "continue",
headers: { "x-request-id": crypto.randomUUID() },
};
};

Attach interceptors to operations via x-plenum-interceptor in an overlay:

actions:
- target: "$.paths['/products'].get"
update:
x-plenum-interceptor:
- module: "./interceptors/add-header.js"
hook: on_request
function: onRequest

To apply to all operations of a method:

actions:
- target: $.paths[*].get
update:
x-plenum-interceptor:
- module: "./interceptors/add-header.js"
hook: on_request
function: onRequest

Interceptors fire at specific points in the request lifecycle:

HookInputCan short-circuitDescription
on_request_headersHeaders onlyYesBefore request body is read
on_requestHeaders + bodyYesAfter full request body is buffered
before_upstreamHeadersNoModify upstream request headers
on_responseResponse headersNoAfter upstream response headers received
on_response_bodyResponse headers + bodyNoAfter full response body buffered

For a detailed walkthrough of each hook, when it fires, and what data it has access to, see Lifecycle Hooks.

Note: on_response_body requires buffer-response: true on the upstream config. See Body Buffering for details on how buffering works, body types, and binary handling.

Passes the request to the next phase. Optionally modifies headers and shares context:

{
action: "continue",
headers: { "x-custom": "value", "x-remove-me": null }, // null deletes a header
ctx: { userId: "123" }, // shared with subsequent interceptors
}

Returns a response directly without reaching the upstream. Only available in on_request_headers and on_request hooks:

{
action: "respond",
status: 403,
headers: { "content-type": "application/json" },
body: { error: "Forbidden" },
}

Interceptors can share data via the ctx object. Values set in ctx are merged across interceptor calls within the same request:

// First interceptor (on_request_headers)
exports.extractUser = function extractUser(request) {
return {
action: "continue",
ctx: { userId: request.headers["x-user-id"] },
};
};
// Later interceptor (on_request) — reads from ctx
exports.checkAuth = function checkAuth(request) {
const userId = request.ctx.userId; // set by previous interceptor
if (!userId) {
return { action: "respond", status: 401, body: { error: "Unauthorized" } };
}
return { action: "continue" };
};

Multiple interceptors can be attached to the same operation. They execute in the order listed:

x-plenum-interceptor:
- module: "./interceptors/auth.js"
hook: on_request_headers
function: checkApiKey
- module: "./interceptors/logging.js"
hook: on_request
function: logRequest
- module: "./interceptors/transform.js"
hook: on_response
function: addHeaders

If any interceptor short-circuits, subsequent interceptors in that phase are skipped.

Static configuration can be passed to interceptors via options:

x-plenum-interceptor:
- module: "./interceptors/rate-limit.js"
hook: on_request_headers
function: checkRate
options:
maxRequests: 100
windowMs: 60000

Accessed in the interceptor via request.options:

exports.checkRate = function checkRate(request) {
const { maxRequests, windowMs } = request.options;
// ...
};

The function receives a request or response object depending on the hook:

Request hooks (on_request_headers, on_request, before_upstream)

Section titled “Request hooks (on_request_headers, on_request, before_upstream)”
FieldDescription
methodHTTP method
pathFull request path
routeOpenAPI path template (e.g. /products/{id})
headersRequest headers
queryRaw query string
queryParamsParsed query parameters (Record<string, unknown>), typed per the OpenAPI spec
paramsPath parameters (Record<string, unknown>), coerced to the declared schema type (integer, boolean, etc.)
ctxContext bag (shared across interceptors)
bodyRequest body (on_request only)
optionsPer-interceptor config from overlay

Response hooks (on_response, on_response_body)

Section titled “Response hooks (on_response, on_response_body)”
FieldDescription
statusResponse status code
headersResponse headers
ctxContext bag
bodyResponse body (on_response_body only)

See Lifecycle Hooks for a full matrix showing which fields are available at each stage.

Plenum includes several built-in interceptors that can be referenced by internal: prefix. See the Built-in Modules section for detailed reference pages, including config options, error responses, and examples for each:

Each interceptor can have its own timeout:

x-plenum-interceptor:
- module: "./interceptors/slow-check.js"
hook: on_request
function: check
timeout-ms: 5000 # 5 second timeout for this interceptor

The effective timeout is the lesser of the interceptor timeout and the remaining request budget. See Timeouts for details.