Gateway Plugin System
AgentWatch provides a sandboxed plugin system that lets administrators extend gateway behavior with custom JavaScript logic — no core code changes, no restarts. Plugins run in an isolated Node.js vm context and cannot access the filesystem, network, environment variables, require, import, process, Buffer, fs, or timers.
How Plugins Execute
Section titled “How Plugins Execute”Inbound Request │ ▼┌─────────────────────────────────────────────────────────────┐│ pre-request plugins (sorted by priority, lowest runs first) ││ Can: modify request body/headers, short-circuit with error │└─────────────────────────────────────────────────────────────┘ │ ▼ (forwarded to upstream LLM) │┌─────────────────────────────────────────────────────────────┐│ post-request plugins ││ Can: inspect request metadata after forwarding │└─────────────────────────────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────┐│ pre-response plugins ││ Can: modify response body/headers before sending to client │└─────────────────────────────────────────────────────────────┘ │ ▼ (response sent to client) │┌─────────────────────────────────────────────────────────────┐│ post-response plugins (async, non-blocking) ││ Can: fire-and-forget logging, metrics │└─────────────────────────────────────────────────────────────┘
on-error plugins fire only when a gateway error occurs at any stage.Fail-open design: If a plugin throws an unhandled exception, the error is logged and the request proceeds normally. Plugins can never crash the gateway.
Execution order: Within the same hook, plugins run by priority ascending (lower number = runs first).
Plugin Fields
Section titled “Plugin Fields”| Field | Required | Description |
|---|---|---|
| Name | Yes | Identifier for the plugin (e.g., add-request-id) |
| Description | No | What the plugin does |
| Hook | Yes | Which lifecycle stage to run at (see hooks below) |
| Route Filter | No | Glob pattern to limit which routes trigger this plugin. Default * = all routes |
| Priority | No | Execution order within the same hook. Lower = runs first. Default 0 |
| Timeout (ms) | No | Max execution time before the plugin is killed. Default 5000ms, max 30000ms |
| Enabled | Yes | Toggle on/off without deleting |
| Plugin Code | Yes | JavaScript source. Must return { proceed: true } or { proceed: false, ... } |
Hook Options
Section titled “Hook Options”| Hook | When It Fires | Context Available | Typical Use |
|---|---|---|---|
pre-request |
Before forwarding to upstream | ctx.request, ctx.route |
Add/remove headers, validate body, block requests |
post-request |
After upstream receives request, before response processing | ctx.request, ctx.route |
Log request metadata |
pre-response |
Before sending upstream response back to client | ctx.request, ctx.response, ctx.route |
Transform or redact response body |
post-response |
After response sent to client (async) | ctx.request, ctx.response, ctx.route |
Fire-and-forget logging, metrics |
on-error |
When any gateway error occurs | ctx.request, ctx.route |
Custom error responses, alerting |
Route Filter Patterns
Section titled “Route Filter Patterns”| Pattern | Matches |
|---|---|
* |
All routes |
chat-* |
Routes whose name starts with chat- |
*/production |
Routes ending in /production |
** |
All routes including nested names |
openai-gpt4 |
Exactly the route named openai-gpt4 |
Plugin Context API (ctx)
Section titled “Plugin Context API (ctx)”Every plugin receives a ctx object (also aliased as context):
ctx.request = { method, // "POST" path, // "/v1/chat/completions" headers, // { "authorization": "Bearer ...", ... } body, // parsed JSON body object query // query string params as object}
ctx.response = { // only in post-request, pre-response, post-response statusCode, // 200, 400, etc. headers, // response headers body // parsed JSON body}
ctx.route = { name, // route display name id, // route UUID upstream // upstream URL}
ctx.config // values from plugin's config field (JSON object)
ctx.logger.info("message", { optional: "data" })ctx.logger.warn("message")ctx.logger.error("message")
ctx.metrics.increment("my-counter")ctx.metrics.record("latency-ms", 42)Return Values
Section titled “Return Values”Every plugin must return an object with at least proceed:
Continue normally:
return { proceed: true };Modify the request:
return { proceed: true, modifiedRequest: { method: ctx.request.method, path: ctx.request.path, headers: { ...ctx.request.headers, "X-Request-Id": "abc123" }, body: ctx.request.body, query: ctx.request.query }};Modify the response:
return { proceed: true, modifiedResponse: { statusCode: ctx.response.statusCode, headers: ctx.response.headers, body: { ...ctx.response.body, _source: "agentwatch" } }};Short-circuit (block the request):
return { proceed: false, statusCode: 403, body: { error: "Blocked by plugin", code: "PLUGIN_BLOCKED" }};Step-by-Step: Creating a Plugin
Section titled “Step-by-Step: Creating a Plugin”Step 1: Navigate to Plugins
- Log in to the AgentWatch Admin Panel
- In the left sidebar, go to Gateway → Plugins
- Click + New Plugin
Step 2: Set the Name and Description
- Name: Short, kebab-case identifier, e.g.,
add-correlation-id - Description: What the plugin does, e.g.,
Injects X-Correlation-Id header on every request
Step 3: Choose a Hook
Pick the hook that matches when your logic should run:
- Modifying the request before it reaches the LLM → pre-request
- Transforming the LLM response → pre-response
- Logging after the response is sent → post-response
- Custom error handling → on-error
Step 4: Set the Route Filter
- Leave as
*to apply to all gateway routes - Enter a specific pattern like
openai-*to target only matching routes
Step 5: Set Priority and Timeout
- Priority: If multiple plugins share the same hook, lower priority number runs first. Use
0for most plugins. - Timeout: Default
5000msis appropriate for most logic. Increase for plugins making external calls via shared state. Decrease for lightweight header manipulations.
Step 6: Write the Plugin Code
Start from a template (see below) or write custom logic. The code must always return { proceed: true } or { proceed: false, ... }.
Step 7: Save and Test
- Click Save Plugin
- On the plugin row, click Test to run the plugin against a sample context
- Click Logs to view execution logs (last 100 entries per plugin)
Built-in Templates
Section titled “Built-in Templates”Template 1: Log Request Logs every request’s method, path, and headers.
ctx.logger.info("Incoming request", { method: ctx.request.method, path: ctx.request.path, headers: ctx.request.headers, query: ctx.request.query});return { proceed: true };Template 2: Add Header
Adds custom headers to the request (reads from ctx.config.headers).
var headers = ctx.request.headers || {};var configHeaders = ctx.config.headers || {};var keys = Object.keys(configHeaders);for (var i = 0; i < keys.length; i++) { headers[keys[i]] = configHeaders[keys[i]];}return { proceed: true, modifiedRequest: { method: ctx.request.method, path: ctx.request.path, headers: headers, body: ctx.request.body, query: ctx.request.query }};Template 3: Rate Limit by Header Blocks requests missing a required header.
var headerName = ctx.config.headerName || "x-api-key";var key = ctx.request.headers[headerName];if (!key) { return { proceed: false, statusCode: 401, body: { error: "Missing required header: " + headerName } };}ctx.logger.info("Header present", { key: key });return { proceed: true };Template 4: Transform Body Adds or removes fields from the request body.
var body = ctx.request.body || {};var addFields = ctx.config.addFields || {};var removeFields = ctx.config.removeFields || [];var keys = Object.keys(addFields);for (var i = 0; i < keys.length; i++) { body[keys[i]] = addFields[keys[i]];}for (var j = 0; j < removeFields.length; j++) { delete body[removeFields[j]];}return { proceed: true, modifiedRequest: { method: ctx.request.method, path: ctx.request.path, headers: ctx.request.headers, body: body, query: ctx.request.query }};Sandbox Restrictions
Section titled “Sandbox Restrictions”The following are not available inside plugin code:
| Blocked | Reason |
|---|---|
require / import |
No module loading |
fetch / XMLHttpRequest |
No outbound network calls |
process / global |
No server environment access |
fs / child_process |
No filesystem or shell access |
setTimeout / setInterval |
No async timers |
Buffer |
No raw binary access |
Use ctx.config to pass configuration values into the plugin rather than hardcoding secrets.
API Reference
Section titled “API Reference”| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/gateway/plugins |
List all plugins |
| GET | /api/admin/gateway/plugins/:id |
Get a single plugin |
| POST | /api/admin/gateway/plugins |
Create a new plugin |
| PUT | /api/admin/gateway/plugins/:id |
Update a plugin |
| DELETE | /api/admin/gateway/plugins/:id |
Delete a plugin |
| POST | /api/admin/gateway/plugins/:id/test |
Test-execute a plugin in sandbox |
| GET | /api/admin/gateway/plugins/:id/logs |
Get last 100 execution logs |